From 054506c57318fa4a2dbd05dd264f6f3af3d703cc Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 16:55:13 -0600 Subject: [PATCH 001/126] Generated DependencyPropertyGenerator component from template --- .../OpenSolution.bat | 3 ++ .../samples/Assets/icon.png | Bin 0 -> 2216 bytes .../samples/Dependencies.props | 31 +++++++++++++++++ ...DependencyPropertyGenerator.Samples.csproj | 10 ++++++ .../samples/DependencyPropertyGenerator.md | 32 ++++++++++++++++++ ...pendencyPropertyGeneratorCustomSample.xaml | 8 +++++ ...dencyPropertyGeneratorCustomSample.xaml.cs | 30 ++++++++++++++++ ...ontrols.DependencyPropertyGenerator.csproj | 15 ++++++++ .../src/MultiTarget.props | 9 +++++ ...ependencyPropertyGenerator.Tests.projitems | 23 +++++++++++++ .../DependencyPropertyGenerator.Tests.shproj | 13 +++++++ tooling | 2 +- 12 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/OpenSolution.bat create mode 100644 components/DependencyPropertyGenerator/samples/Assets/icon.png create mode 100644 components/DependencyPropertyGenerator/samples/Dependencies.props create mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.Samples.csproj create mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md create mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml create mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs create mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj create mode 100644 components/DependencyPropertyGenerator/src/MultiTarget.props create mode 100644 components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems create mode 100644 components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.shproj diff --git a/components/DependencyPropertyGenerator/OpenSolution.bat b/components/DependencyPropertyGenerator/OpenSolution.bat new file mode 100644 index 000000000..814a56d4b --- /dev/null +++ b/components/DependencyPropertyGenerator/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/samples/Assets/icon.png b/components/DependencyPropertyGenerator/samples/Assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8435bcaa9fc371ca8e92db07ae596e0d57c8b9b0 GIT binary patch literal 2216 zcmV;Z2v_%sP);M1&0drDELIAGL9O(c600d`2O+f$vv5yP=YKs(Je9p7&Ka&|n*ecc!Pix~iV~>Yi6*wXL?Fq6O}_ef#!O2;q#X&d1-r zFW&c8*L5NOYWz*lA^xUE>kGNx-uL6v^z@vr)V_TA(vQ#Yoo1$I^Fv;IKkA`?U*Z6OoBe{XX@DLpL{F3+>RV2oP732rn@P@98F ziH~#f`tA7f<8t}(<$sLlUkdm_STvOKGYXY{9St3tGiu1>duN9F9aTe*kTXOCkoLYj zr)5cJP?i}f+kBoB8b~Q1rbJi`730DXGLx}aCV-6tC522QjZs)X03S%#=jXopQNe7G z@dlToiDbGDSy!a_DD)NYWiV?sGap0Dq-qgnA&~LHECw(NL8T=) z1ct(ga2;|14nD@qHwmVQ0;2|YUomW^!U#`7l>>&Bh;u)pT<|$jFoqk6%HTc~^RQ@( z5hZ5X^n7`vt!*nY9@rFRqF{^wF`}&H4I4JdfddC*W@e@$oS$Q04aThBn*gT3)URMp z>G{o@H*)RTHCb6%8B?H}yjcXcUm9p(K=nWD0vP!PaCP$@(k!31bkrIJ!2)-tl96*+&}@I6!3M8qT~Q2?u8 zcy@MHS+IzlwjvzRGx~+*l>(Gi6$!C8Jgi;2)=XVKe*CB(K745TS`)G2;nuBN9cqsP zh3?9y5yI0j3rZt%Q;V3i*L;-@bj}#EBDL zi-O{(`k1i!rOBH%ZPF-Qh^8PnZ{F0;pFa!x1_lMnUFbI&&8gFC}WVB;VU)DL&bgeyDBGT1Uw=yFE1xQ zlXdIXN%Xx|Sv6HKV+J+vFk{k20ni@>s(7s{7```cTQ%Vf8y`xM()#6VjL@l-2UZ)1 zMAlp(2n!()MJZ+A7DT29I(lXL!EfSTFx}nSy)d`h{3}%2w00Npq^YO)x9XqDcq0Kb`t5*xfQqpFjCI=5f3~jf78p^G}no2Fzdc;)IysSSZVr(fukQEprykDy< zqbZnxBN8(`!7W?1$e}}r1c|2>qh?{>d-v{@?c2BeJQ<2<$;_cXbnDiw*59|?yLU^< zSA=eeDMyH&Dn;O?U<@moWoql!uTK{z=_+aO*s+8Ar_R9^^Jcn6#~@GNDp>Q(&lq|B z{JGq_cdrQ3>E_6hBQiff?~J4|4F&T## ze2Ot?F7i1RStkmn!!cL^bKdFtd(+&zckeRXgNN#PA!`aD zjaC7J&2fZoFH{s(d8}#I6o7s`O)w?K`{bKiENsKV!h(MK^r(|LX> z*~rJxUHVoXzp-XhUqnbQUAiRq@89q5e?*H1Nj(o2FJ5%B>%JZY`Gw<0&+dhGuj!C7 z?uYbkO5XY{KIV*@{VRoX8s`fm<068)Y!=w) zn?l)IstDTf!(Iu(8i;vTaeMOuE%QV$RY7$1cP-aqS07(jX4W{bFHp!#3m}TTAaaF3L~n9Q)s^3xP5nw5 zM&HvBw5z{TbdA3!8Di)wIs`5S;W!fVdWDbiH|P}wlVfDMuB&#@so4j+uC6L7Q#M38 z*q?Pnc_gqfql3rn?qh)V@~B|(78+7U zKOCcocD&A`ELB-^?%cVvanNF%vtSH&^_LVuBqdkprWDoUyaLCf$s5zvc?rH#NGXk= qmFA_t_5FR}!i7I&wXL?Ful)xU?DJJ%Hwu*i0000 + + + + + + + + + + + + + + + + + + + + + diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.Samples.csproj b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.Samples.csproj new file mode 100644 index 000000000..c7af9907c --- /dev/null +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.Samples.csproj @@ -0,0 +1,10 @@ + + + + + DependencyPropertyGenerator + + + + + diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md new file mode 100644 index 000000000..35e7939c9 --- /dev/null +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md @@ -0,0 +1,32 @@ +--- +title: DependencyPropertyGenerator +author: githubaccount +description: TODO: Your experiment's description here +keywords: DependencyPropertyGenerator, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + + + + + + + + + +# DependencyPropertyGenerator + +TODO: Fill in information about this experiment and how to get started here... + +## Custom Control + +You can inherit from an existing component as well, like `Panel`, this example shows a control without a +XAML Style that will be more light-weight to consume by an app developer: + +> [!Sample DependencyPropertyGeneratorCustomSample] diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml new file mode 100644 index 000000000..0d45a88ec --- /dev/null +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml @@ -0,0 +1,8 @@ + + + + diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs new file mode 100644 index 000000000..1b07afdd3 --- /dev/null +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs @@ -0,0 +1,30 @@ +// 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 CommunityToolkit.WinUI.Controls; + +namespace DependencyPropertyGeneratorExperiment.Samples; + +/// +/// An example sample page of a custom control inheriting from Panel. +/// +[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] +[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] + +[ToolkitSample(id: nameof(DependencyPropertyGeneratorCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(DependencyPropertyGenerator)} custom control.")] +public sealed partial class DependencyPropertyGeneratorCustomSample : Page +{ + public DependencyPropertyGeneratorCustomSample() + { + this.InitializeComponent(); + } + + // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 + public static Orientation ConvertStringToOrientation(string orientation) => orientation switch + { + "Vertical" => Orientation.Vertical, + "Horizontal" => Orientation.Horizontal, + _ => throw new System.NotImplementedException(), + }; +} diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj new file mode 100644 index 000000000..84f505cc8 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj @@ -0,0 +1,15 @@ + + + + + DependencyPropertyGenerator + This package contains DependencyPropertyGenerator. + + + CommunityToolkit.WinUI.Controls.DependencyPropertyGeneratorRns + false + + + + + diff --git a/components/DependencyPropertyGenerator/src/MultiTarget.props b/components/DependencyPropertyGenerator/src/MultiTarget.props new file mode 100644 index 000000000..b11c19426 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems new file mode 100644 index 000000000..3156f4a9f --- /dev/null +++ b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 22BE50B3-9810-4304-899E-6D7AF9D3147A + + + DependencyPropertyGeneratorExperiment.Tests + + + + + ExampleDependencyPropertyGeneratorTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.shproj b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.shproj new file mode 100644 index 000000000..65d342261 --- /dev/null +++ b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.shproj @@ -0,0 +1,13 @@ + + + + 22BE50B3-9810-4304-899E-6D7AF9D3147A + 14.0 + + + + + + + + diff --git a/tooling b/tooling index d71b08b2d..2ec08a780 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit d71b08b2dccf94c3ceaeda99526679bc0cfc3b8a +Subproject commit 2ec08a780daaecadd9705a2ab0e4cdde4c6168e2 From 4ed617295954a67806dbe8668e63bd01266fde92 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 15:07:48 -0800 Subject: [PATCH 002/126] Port files from private repo --- .../AnalyzerReleases.Shipped.md | 20 + .../AnalyzerReleases.Unshipped.md | 2 + ...dDependencyProperty.SourceGenerator.csproj | 30 + .../Constants/WellKnownTrackingNames.cs | 26 + .../Constants/WellKnownTypeNames.cs | 47 + .../DependencyPropertyGenerator.Execute.cs | 763 +++++++ .../DependencyPropertyGenerator.cs | 174 ++ ...dPropertyConflictingDeclarationAnalyzer.cs | 70 + ...opertyContainingTypeDeclarationAnalyzer.cs | 65 + ...nvalidPropertyDefaultValueTypeAttribute.cs | 128 ++ ...dPropertyNonNullableDeclarationAnalyzer.cs | 70 + ...nvalidPropertySymbolDeclarationAnalyzer.cs | 100 + ...nvalidPropertySyntaxDeclarationAnalyzer.cs | 102 + ...nsupportedCSharpLanguageVersionAnalyzer.cs | 78 + .../Diagnostics/DiagnosticDescriptors.cs | 156 ++ .../Extensions/AccessibilityExtensions.cs | 32 + .../Extensions/AttributeDataExtensions.cs | 117 + .../Extensions/CompilationExtensions.cs | 45 + .../Extensions/ISymbolExtensions.cs | 94 + .../Extensions/ITypeSymbolExtensions.cs | 139 ++ .../IncrementalValueProviderExtensions.cs | 71 + .../IndentedTextWriterExtensions.cs | 104 + .../Extensions/SyntaxNodeExtensions.cs | 76 + .../Helpers/EquatableArray{T}.cs | 216 ++ .../Helpers/HashCode.cs | 503 +++++ .../Helpers/ImmutableArrayBuilder{T}.cs | 365 +++ .../Helpers/IndentedTextWriter.cs | 515 +++++ .../Helpers/ObjectPool{T}.cs | 154 ++ .../Models/DependencyPropertyInfo.cs | 40 + .../Models/HierarchyInfo.cs | 140 ++ .../Models/TypeInfo.cs | 32 + .../Models/TypedConstantInfo.Factory.cs | 60 + .../Models/TypedConstantInfo.cs | 187 ++ ...t.GeneratedDependencyProperty.Tests.csproj | 41 + .../Helpers/CSharpAnalyzerTest{TAnalyzer}.cs | 60 + .../CSharpGeneratorTest{TGenerator}.cs | 220 ++ .../Test_Analyzers.cs | 746 +++++++ ...ncyPropertyGenerator.PostInitialization.cs | 48 + .../Test_DependencyPropertyGenerator.cs | 1962 +++++++++++++++++ ...Toolkit.GeneratedDependencyProperty.csproj | 10 + ...oolkit.GeneratedDependencyProperty.targets | 70 + .../src/GeneratedDependencyProperty.cs | 24 + .../GeneratedDependencyPropertyAttribute.cs | 63 + 43 files changed, 7965 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs create mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj create mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets create mode 100644 components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs create mode 100644 components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md new file mode 100644 index 000000000..a7615b210 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md @@ -0,0 +1,20 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +## Release 1.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +WCTDP0001 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0002 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0003 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0004 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0005 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0006 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0007 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0008 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0009 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | +WCTDP0010 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | +WCTDP0011 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md new file mode 100644 index 000000000..6a6dd08d1 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md @@ -0,0 +1,2 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj new file mode 100644 index 000000000..b64446802 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj @@ -0,0 +1,30 @@ + + + netstandard2.0 + 13.0 + enable + true + true + $(DefineConstants);WINDOWS_UWP + + + $(NoWarn);IDE0130 + + + + + + + + + + + + + + + + + + + diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs new file mode 100644 index 000000000..1539c7ea0 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs @@ -0,0 +1,26 @@ +// 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. + +namespace CommunityToolkit.GeneratedDependencyProperty.Constants; + +/// +/// The well known names for tracking steps, to test the incremental generators. +/// +internal static class WellKnownTrackingNames +{ + /// + /// The initial transform node. + /// + public const string Execute = nameof(Execute); + + /// + /// The filtered transform with just output diagnostics. + /// + public const string Diagnostics = nameof(Diagnostics); + + /// + /// The filtered transform with just output sources. + /// + public const string Output = nameof(Output); +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs new file mode 100644 index 000000000..cc2808537 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs @@ -0,0 +1,47 @@ +// 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. + +namespace CommunityToolkit.GeneratedDependencyProperty.Constants; + +/// +/// The well known names for types used by source generators and analyzers. +/// +internal static class WellKnownTypeNames +{ + /// + /// The fully qualified type name for the [GeneratedDependencyProperty] type. + /// + public const string GeneratedDependencyPropertyAttribute = "CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"; + + /// + /// The fully qualified type name for the XAML namespace. + /// + public const string XamlNamespace = +#if WINDOWS_UWP + "Windows.UI.Xaml"; + +#else + "Microsoft.UI.Xaml"; +#endif + + /// + /// The fully qualified type name for the DependencyObject type. + /// + public const string DependencyObject = $"{XamlNamespace}.{nameof(DependencyObject)}"; + + /// + /// The fully qualified type name for the DependencyProperty type. + /// + public const string DependencyProperty = $"{XamlNamespace}.{nameof(DependencyProperty)}"; + + /// + /// The fully qualified type name for the DependencyPropertyChangedEventArgs type. + /// + public const string DependencyPropertyChangedEventArgs = $"{XamlNamespace}.{nameof(DependencyPropertyChangedEventArgs)}"; + + /// + /// The fully qualified type name for the PropertyMetadata type. + /// + public const string PropertyMetadata = $"{XamlNamespace}.{nameof(PropertyMetadata)}"; +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs new file mode 100644 index 000000000..aa5164dba --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs @@ -0,0 +1,763 @@ +// 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.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using CommunityToolkit.GeneratedDependencyProperty.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +partial class DependencyPropertyGenerator +{ + /// + /// A container for all the logic for . + /// + private static partial class Execute + { + /// + /// Placeholder for . + /// + private static readonly TypedConstantInfo.Null NullInfo = new(); + + /// + /// Placeholder for the unset value of a given property type. + /// + private static readonly TypedConstantInfo.UnsetValue UnsetValueInfo = new(); + + /// + /// Generates the sources for the embedded types, for PrivateAssets="all" scenarios. + /// + /// The input value to use to emit sources. + public static void GeneratePostInitializationSources(IncrementalGeneratorPostInitializationContext context) + { + void GenerateSource(string typeName) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + string fileName = $"{typeName}.g.cs"; + string sourceText; + + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fileName)) + using (StreamReader reader = new(stream)) + { + sourceText = reader.ReadToEnd(); + } + + context.CancellationToken.ThrowIfCancellationRequested(); + + string updatedSourceText = sourceText + .Replace("", GeneratorName) + .Replace("", typeof(Execute).Assembly.GetName().Version.ToString()); + + context.CancellationToken.ThrowIfCancellationRequested(); + + context.AddSource(fileName, updatedSourceText); + } + + GenerateSource("GeneratedDependencyProperty"); + GenerateSource("GeneratedDependencyPropertyAttribute"); + } + + /// + /// Checks whether an input syntax node is a candidate property declaration for the generator. + /// + /// The input syntax node to check. + /// The used to cancel the operation, if needed. + /// Whether is a candidate property declaration. + public static bool IsCandidateSyntaxValid(SyntaxNode node, CancellationToken token) + { + // Initial check that's identical to the analyzer + if (!InvalidPropertySyntaxDeclarationAnalyzer.IsValidPropertyDeclaration(node)) + { + return false; + } + + // Make sure that all containing types are partial, otherwise declaring a partial property + // would not be valid. We don't need to emit diagnostics here, the compiler will handle that. + for (TypeDeclarationSyntax? parentNode = node.FirstAncestor(); + parentNode is not null; + parentNode = parentNode.FirstAncestor()) + { + if (!parentNode.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return false; + } + } + + // Here we can also easily filter out ref-returning properties just using syntax + if (((PropertyDeclarationSyntax)node).Type.IsKind(SyntaxKind.RefType)) + { + return false; + } + + return true; + } + + /// + /// Checks whether an input symbol is a candidate property declaration for the generator. + /// + /// The input symbol to check. + /// Whether is a candidate property declaration. + public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol) + { + // Ensure that the property declaration is a partial definition with no implementation + if (propertySymbol is not { IsPartialDefinition: true, PartialImplementationPart: null }) + { + return false; + } + + // Also ignore all properties that have an invalid declaration + if (propertySymbol.IsStatic || propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly || propertySymbol.Type.IsRefLikeType) + { + return false; + } + + // Ensure we do have a valid containing + if (propertySymbol.ContainingType is not { } typeSymbol) + { + return false; + } + + // Ensure that the containing type derives from 'DependencyObject' + if (!typeSymbol.InheritsFromFullyQualifiedMetadataName(WellKnownTypeNames.DependencyObject)) + { + return false; + } + + // If the generated property name is called "Property" and the type is either object or 'DependencyPropertyChangedEventArgs', + // consider it invalid. This is needed because if such a property was generated, the partial 'OnChanged' + // methods would conflict. + if (propertySymbol.Name == "Property") + { + bool propertyTypeWouldCauseConflicts = + propertySymbol.Type.SpecialType == SpecialType.System_Object || + propertySymbol.Type.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs); + + return !propertyTypeWouldCauseConflicts; + } + + return true; + } + + /// + /// Tries to get the accessibility of the property and accessors, if possible. + /// + /// The input node. + /// The input instance. + /// The accessibility of the property, if available. + /// The accessibility of the accessor, if available. + /// The accessibility of the accessor, if available. + /// Whether the property was valid and the accessibilities could be retrieved. + public static bool TryGetAccessibilityModifiers( + PropertyDeclarationSyntax node, + IPropertySymbol propertySymbol, + out Accessibility declaredAccessibility, + out Accessibility getterAccessibility, + out Accessibility setterAccessibility) + { + declaredAccessibility = Accessibility.NotApplicable; + getterAccessibility = Accessibility.NotApplicable; + setterAccessibility = Accessibility.NotApplicable; + + // Ensure that we have a getter and a setter, and that the setter is not init-only + if (propertySymbol is not { GetMethod: { } getMethod, SetMethod: { IsInitOnly: false } setMethod }) + { + return false; + } + + // Track the property accessibility if explicitly set + if (node.Modifiers.Count > 0) + { + declaredAccessibility = propertySymbol.DeclaredAccessibility; + } + + // Track the accessors accessibility, if explicitly set + foreach (AccessorDeclarationSyntax accessor in node.AccessorList?.Accessors ?? []) + { + if (accessor.Modifiers.Count == 0) + { + continue; + } + + switch (accessor.Kind()) + { + case SyntaxKind.GetAccessorDeclaration: + getterAccessibility = getMethod.DeclaredAccessibility; + break; + case SyntaxKind.SetAccessorDeclaration: + setterAccessibility = setMethod.DeclaredAccessibility; + break; + } + } + + return true; + } + + /// + /// Gets the default value to use to initialize the generated property, if explicitly specified. + /// + /// The input that triggered the annotation. + /// The input instance. + /// The for the current compilation. + /// The used to cancel the operation, if needed. + /// The default value to use to initialize the generated property. + public static TypedConstantInfo GetDefaultValue( + AttributeData attributeData, + IPropertySymbol propertySymbol, + SemanticModel semanticModel, + CancellationToken token) + { + // First, check whether the default value is explicitly set or not + if (attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue)) + { + // If the explicit value is anything other than 'null', we can return it directly + if (!defaultValue.IsNull) + { + return TypedConstantInfo.Create(defaultValue); + } + + // Handle 'UnsetValue' as well + if (InvalidPropertyDefaultValueTypeAttribute.IsDependencyPropertyUnsetValue(attributeData, semanticModel, token)) + { + return UnsetValueInfo; + } + + // Otherwise, the value has been explicitly set to 'null', so let's respect that + return NullInfo; + } + + // In all other cases, we'll automatically use the default value of the type in question. + // First we need to special case non nullable values, as for those we need 'default'. + if (propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }) + { + return new TypedConstantInfo.Default(propertySymbol.Type.GetFullyQualifiedName()); + } + + // For all other ones, we can just use the 'null' placeholder again + return NullInfo; + } + + /// + /// Checks whether the generated code has to register the property changed callback with WinRT. + /// + /// The input that triggered the annotation. + /// Whether the generated should register the property changed callback. + public static bool IsLocalCachingEnabled(AttributeData attributeData) + { + return attributeData.GetNamedArgument("IsLocalCacheEnabled", defaultValue: false); + } + + /// + /// Checks whether the generated code has to register the property changed callback with WinRT. + /// + /// The input instance to process. + /// Whether the generated should register the property changed callback. + public static bool IsPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol) + { + // Check for any 'OnChanged' methods + foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers($"On{propertySymbol.Name}PropertyChanged")) + { + // We're looking for methods with one parameters, so filter on that first + if (symbol is not IMethodSymbol { IsStatic: false, ReturnsVoid: true, Parameters: [{ Type: INamedTypeSymbol argsType }] }) + { + continue; + } + + // There might be other property changed callback methods when field caching is enabled, or in other scenarios. + // Because the callback method existing adds overhead (since we have to register it with WinRT), we want to + // avoid false positives. To do that, we check that the parameter type is exactly the one we need. + if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs)) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether the generated code has to register the shared property changed callback with WinRT. + /// + /// The input instance to process. + /// Whether the generated should register the shared property changed callback. + public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol) + { + // Check for any 'OnPropertyChanged' methods + foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers("OnPropertyChanged")) + { + // Same filter as above + if (symbol is not IMethodSymbol { IsStatic: false, ReturnsVoid: true, Parameters: [{ Type: INamedTypeSymbol argsType }] }) + { + continue; + } + + // Also same actual check as above + if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs)) + { + return true; + } + } + + return false; + } + + /// + /// Checks whether an input property is required. + /// + /// The input instance to process. + /// Whether is required. + public static bool IsRequiredProperty(IPropertySymbol propertySymbol) + { + return propertySymbol.IsRequired; + } + + /// + /// Writes all implementations of partial dependency property declarations. + /// + /// The input set of declared dependency properties. + /// The instance to write into. + public static void WritePropertyDeclarations(EquatableArray propertyInfos, IndentedTextWriter writer) + { + // Helper to get the nullable type name for the initial property value + static string GetOldValueTypeNameAsNullable(DependencyPropertyInfo propertyInfo) + { + // Prepare the nullable type for the previous property value. This is needed because if the type is a reference + // type, the previous value might be null even if the property type is not nullable, as the first invocation would + // happen when the property is first set to some value that is not null (but the backing field would still be so). + // As a cheap way to check whether we need to add nullable, we can simply check whether the type name with nullability + // annotations ends with a '?'. If it doesn't and the type is a reference type, we add it. Otherwise, we keep it. + return propertyInfo.IsReferenceTypeOrUnconstraindTypeParameter switch + { + true when !propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?") + => $"{propertyInfo.TypeNameWithNullabilityAnnotations}?", + _ => propertyInfo.TypeNameWithNullabilityAnnotations + }; + } + + // Helper to get the accessibility with a trailing space + static string GetExpressionWithTrailingSpace(Accessibility accessibility) + { + return accessibility.GetExpression() switch + { + { Length: > 0 } expression => expression + " ", + _ => "" + }; + } + + string typeQualifiedName = propertyInfos[0].Hierarchy.Hierarchy[0].QualifiedName; + + // First, generate all the actual dependency property fields + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) + { + string typeMetadata = propertyInfo switch + { + // Shared codegen + { DefaultValue: TypedConstantInfo.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + => "null", + { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue})", + + // Codegen for legacy UWP + { IsNet8OrGreater: false } => propertyInfo switch + { + { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } + => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", + { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } + => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", + { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: true } + => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", + _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), + }, + + // Codegen for .NET 8 or greater + { DefaultValue: TypedConstantInfo.Null } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) + => $"new global::{WellKnownTypeNames.PropertyMetadata}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + { DefaultValue: { } defaultValue } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) + => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), + }; + + writer.WriteLine($$""" + /// + /// The backing instance for . + /// + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($$""" + public static readonly global::{{WellKnownTypeNames.DependencyProperty}} {{propertyInfo.PropertyName}}Property = global::{{WellKnownTypeNames.DependencyProperty}}.Register( + name: "{{propertyInfo.PropertyName}}", + propertyType: typeof({{propertyInfo.TypeName}}), + ownerType: typeof({{typeQualifiedName}}), + typeMetadata: {{typeMetadata}}); + """, isMultiline: true); + writer.WriteLine(); + } + + // After the properties, generate all partial property implementations at the top of the partial type declaration + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) + { + string oldValueTypeNameAsNullable = GetOldValueTypeNameAsNullable(propertyInfo); + + writer.WriteLine(skipIfPresent: true); + writer.WriteLine("/// "); + writer.WriteGeneratedAttributes(GeneratorName); + writer.Write(GetExpressionWithTrailingSpace(propertyInfo.DeclaredAccessibility)); + writer.WriteIf(propertyInfo.IsRequired, "required "); + writer.WriteLine($"partial {propertyInfo.TypeNameWithNullabilityAnnotations} {propertyInfo.PropertyName}"); + + using (writer.WriteBlock()) + { + // We need very different codegen depending on whether local caching is enabled or not + if (propertyInfo.IsLocalCachingEnabled) + { + writer.WriteLine($$""" + get => field; + set + { + On{{propertyInfo.PropertyName}}Set(ref value); + + if (global::System.Collections.Generic.EqualityComparer<{{oldValueTypeNameAsNullable}}>.Default.Equals(field, value)) + { + return; + } + + {{oldValueTypeNameAsNullable}} __oldValue = field; + + On{{propertyInfo.PropertyName}}Changing(value); + On{{propertyInfo.PropertyName}}Changing(__oldValue, value); + + field = value; + + object? __boxedValue = value; + """, isMultiline: true); + writer.WriteLineIf(propertyInfo.TypeName != "object", $""" + + On{propertyInfo.PropertyName}Set(ref __boxedValue); + """, isMultiline: true); + writer.Write($$""" + + SetValue({{propertyInfo.PropertyName}}Property, __boxedValue); + + On{{propertyInfo.PropertyName}}Changed(value); + On{{propertyInfo.PropertyName}}Changed(__oldValue, value); + } + """, isMultiline: true); + + // If the default value is not what the default field value would be, add an initializer + if (propertyInfo.DefaultValue is not (TypedConstantInfo.Null or TypedConstantInfo.Default)) + { + writer.Write($" = {propertyInfo.DefaultValue};"); + } + + // Always leave a newline after the end of the property declaration, in either case + writer.WriteLine(); + } + else if (propertyInfo.TypeName == "object") + { + // If local caching is not enabled, we simply relay to the 'DependencyProperty' value. We cannot raise any methods + // to explicitly notify of changes that rely on the previous value. Retrieving it to conditionally invoke the methods + // would introduce a lot of overhead. If callers really do want to have a callback being invoked, they can implement + // the one wired up to the property metadata directly. We can still invoke the ones only using the new value, though. + writer.WriteLine($$""" + get + { + object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property); + + On{{propertyInfo.PropertyName}}Get(ref __boxedValue); + + return __boxedValue; + } + set + { + On{{propertyInfo.PropertyName}}Set(ref value); + + SetValue({{propertyInfo.PropertyName}}Property, value); + + On{{propertyInfo.PropertyName}}Changed(value); + } + """, isMultiline: true); + } + else + { + // Same as above but with the extra typed hook for both accessors + writer.WriteLine($$""" + get + { + object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property); + + On{{propertyInfo.PropertyName}}Get(ref __boxedValue); + + {{propertyInfo.TypeNameWithNullabilityAnnotations}} __unboxedValue = ({{propertyInfo.TypeNameWithNullabilityAnnotations}})__boxedValue; + + On{{propertyInfo.PropertyName}}Get(ref __unboxedValue); + + return __unboxedValue; + } + set + { + On{{propertyInfo.PropertyName}}Set(ref value); + + object? __boxedValue = value; + + On{{propertyInfo.PropertyName}}Set(ref __boxedValue); + + SetValue({{propertyInfo.PropertyName}}Property, __boxedValue); + + On{{propertyInfo.PropertyName}}Changed(value); + } + """, isMultiline: true); + + } + } + } + + // Next, emit all partial method declarations at the bottom of the file + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) + { + string oldValueTypeNameAsNullable = GetOldValueTypeNameAsNullable(propertyInfo); + string objectTypeNameWithNullabilityAnnotation = propertyInfo.TypeNameWithNullabilityAnnotations.EndsWith("?") ? "object?" : "object"; + + if (!propertyInfo.IsLocalCachingEnabled) + { + // OnGet 'object' overload (only without local caching, as otherwise we just return the field value) + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Get(ref {objectTypeNameWithNullabilityAnnotation} propertyValue);"); + + // OnGet typed overload + if (propertyInfo.TypeName != "object") + { + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Get(ref {propertyInfo.TypeNameWithNullabilityAnnotations} propertyValue);"); + } + } + + // OnSet 'object' overload + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Set(ref {objectTypeNameWithNullabilityAnnotation} propertyValue);"); + + if (propertyInfo.TypeName != "object") + { + // OnSet typed overload + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Set(ref {propertyInfo.TypeNameWithNullabilityAnnotations} propertyValue);"); + } + + // We can only generate the direct callback methods when using local caching (see notes above) + if (propertyInfo.IsLocalCachingEnabled) + { + // OnChanging, only with new value + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when is changing. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Changing({propertyInfo.TypeNameWithNullabilityAnnotations} newValue);"); + + // OnChanging, with both values + writer.WriteLine(); + writer.WriteLine($""" + /// 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. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Changing({oldValueTypeNameAsNullable} oldValue, {propertyInfo.TypeNameWithNullabilityAnnotations} newValue);"); + } + + // OnChanged, only with new value (this is always supported) + writer.WriteLine(skipIfPresent: true); + writer.WriteLine($""" + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Changed({propertyInfo.TypeNameWithNullabilityAnnotations} newValue);"); + + // OnChanged, with both values (once again, this is only supported when local caching is enabled) + if (propertyInfo.IsLocalCachingEnabled) + { + writer.WriteLine(); + writer.WriteLine($""" + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}Changed({oldValueTypeNameAsNullable} oldValue, {propertyInfo.TypeNameWithNullabilityAnnotations} newValue);"); + } + + // OnChanged, for the property metadata callback + writer.WriteLine(); + writer.WriteLine($""" + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}PropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); + } + + // OnPropertyChanged, for the shared property metadata callback + writer.WriteLine(); + writer.WriteLine($""" + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + writer.WriteLine($"partial void OnPropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); + } + + /// + /// Checks whether additional types are required for the input set of properties. + /// + /// The input set of declared dependency properties. + /// Whether additional types are required. + public static bool RequiresAdditionalTypes(EquatableArray propertyInfos) + { + // If the target is not .NET 8, we never need additional types (as '[UnsafeAccessor]' is not available) + if (!propertyInfos[0].IsNet8OrGreater) + { + return false; + } + + // We need the additional type holding the generated callbacks if at least one WinRT-based callback is present + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) + { + if (propertyInfo.IsPropertyChangedCallbackImplemented || propertyInfo.IsSharedPropertyChangedCallbackImplemented) + { + return true; + } + } + + return false; + } + + /// + /// Registers a callback to generate additional types, if needed. + /// + /// The input set of declared dependency properties. + /// The instance to write into. + public static void WriteAdditionalTypes(EquatableArray propertyInfos, IndentedTextWriter writer) + { + writer.WriteLine("using global::System.Runtime.CompilerServices;"); + writer.WriteLine($"using global::{WellKnownTypeNames.XamlNamespace};"); + writer.WriteLine(); + writer.WriteLine($$""" + /// + /// Contains shared property changed callbacks for . + /// + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName); + writer.WriteLine("file static class PropertyChangedCallbacks"); + + using (writer.WriteBlock()) + { + string fullyQualifiedTypeName = propertyInfos[0].Hierarchy.GetFullyQualifiedTypeName(); + + // Write the public accessors to use in property initializers + writer.WriteLineSeparatedMembers(propertyInfos.AsSpan(), (propertyInfo, writer) => + { + writer.WriteLine($$""" + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback {{propertyInfo.PropertyName}}() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + {{fullyQualifiedTypeName}} __this = ({{fullyQualifiedTypeName}})d; + + """, isMultiline: true); + writer.IncreaseIndent(); + writer.IncreaseIndent(); + + // Per-property callback, if present + if (propertyInfo.IsPropertyChangedCallbackImplemented) + { + writer.WriteLine($"On{propertyInfo.PropertyName}PropertyChanged(__this, e);"); + } + + // Shared callback, if present + if (propertyInfo.IsSharedPropertyChangedCallbackImplemented) + { + writer.WriteLine("OnPropertyChanged(__this, e);"); + } + + // Close the method and return the 'Invoke' method as a delegate (just one allocation here) + writer.DecreaseIndent(); + writer.DecreaseIndent(); + writer.WriteLine(""" + } + + return new(Invoke); + } + """, isMultiline: true); + }); + + // Write the accessors for all WinRT-based callbacks (not the shared one) + foreach (DependencyPropertyInfo propertyInfo in propertyInfos.Where(static property => property.IsPropertyChangedCallbackImplemented)) + { + writer.WriteLine(); + writer.WriteLine($""" + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "On{propertyInfo.PropertyName}PropertyChanged")] + private static extern void On{propertyInfo.PropertyName}PropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); + """, isMultiline: true); + } + + // Also emit one for the shared callback, if it's ever used + if (propertyInfos.Any(static property => property.IsSharedPropertyChangedCallbackImplemented)) + { + writer.WriteLine(); + writer.WriteLine($""" + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + private static extern void OnPropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); + """, isMultiline: true); + } + } + } + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs new file mode 100644 index 000000000..d82418f23 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs @@ -0,0 +1,174 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using CommunityToolkit.GeneratedDependencyProperty.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A source generator creating implementations of dependency properties. +/// +[Generator(LanguageNames.CSharp)] +public sealed partial class DependencyPropertyGenerator : IIncrementalGenerator +{ + /// + /// The name of generator to include in the generated code. + /// + internal const string GeneratorName = "CommunityToolkit.WinUI.DependencyPropertyGenerator"; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Generate the sources for the 'PrivateAssets="all"' mode + context.RegisterPostInitializationOutput(Execute.GeneratePostInitializationSources); + + // Get the info on all dependency properties to generate + IncrementalValuesProvider propertyInfo = + context.SyntaxProvider + .ForAttributeWithMetadataName( + WellKnownTypeNames.GeneratedDependencyPropertyAttribute, + Execute.IsCandidateSyntaxValid, + static (context, token) => + { + // We need C# 13, double check that it's the case + if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp13)) + { + return null; + } + + bool isLocalCachingEnabled = Execute.IsLocalCachingEnabled(context.Attributes[0]); + + // This generator requires C# preview to be used (due to the use of the 'field' keyword). + // The 'field' keyword is actually only used when local caching is enabled, so filter to that. + if (!isLocalCachingEnabled && !context.SemanticModel.Compilation.IsLanguageVersionPreview()) + { + return null; + } + + token.ThrowIfCancellationRequested(); + + // Ensure we do have a property + if (context.TargetSymbol is not IPropertySymbol propertySymbol) + { + return null; + } + + // Do an initial filtering on the symbol as well + if (!Execute.IsCandidateSymbolValid(propertySymbol)) + { + return null; + } + + token.ThrowIfCancellationRequested(); + + // Get the accessibility values, if the property is valid + if (!Execute.TryGetAccessibilityModifiers( + node: (PropertyDeclarationSyntax)context.TargetNode, + propertySymbol: propertySymbol, + out Accessibility declaredAccessibility, + out Accessibility getterAccessibility, + out Accessibility setterAccessibility)) + { + return default; + } + + token.ThrowIfCancellationRequested(); + + string typeName = propertySymbol.Type.GetFullyQualifiedName(); + string typeNameWithNullabilityAnnotations = propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(); + + token.ThrowIfCancellationRequested(); + + bool isRequired = Execute.IsRequiredProperty(propertySymbol); + bool isPropertyChangedCallbackImplemented = Execute.IsPropertyChangedCallbackImplemented(propertySymbol); + bool isSharedPropertyChangedCallbackImplemented = Execute.IsSharedPropertyChangedCallbackImplemented(propertySymbol); + bool isNet8OrGreater = !context.SemanticModel.Compilation.IsWindowsRuntimeApplication(); + + token.ThrowIfCancellationRequested(); + + // We're using IsValueType here and not IsReferenceType to also cover unconstrained type parameter cases. + // This will cover both reference types as well T when the constraints are not struct or unmanaged. + // If this is true, it means the field storage can potentially be in a null state (even if not annotated). + bool isReferenceTypeOrUnconstraindTypeParameter = !propertySymbol.Type.IsValueType; + + // Also get the default value (this might be slightly expensive, so do it towards the end) + TypedConstantInfo defaultValue = Execute.GetDefaultValue( + context.Attributes[0], + propertySymbol, + context.SemanticModel, + token); + + // The 'UnsetValue' can only be used when local caching is disabled + if (defaultValue is TypedConstantInfo.UnsetValue && isLocalCachingEnabled) + { + return null; + } + + token.ThrowIfCancellationRequested(); + + // Finally, get the hierarchy too + HierarchyInfo hierarchyInfo = HierarchyInfo.From(propertySymbol.ContainingType); + + token.ThrowIfCancellationRequested(); + + return new DependencyPropertyInfo( + Hierarchy: hierarchyInfo, + PropertyName: propertySymbol.Name, + DeclaredAccessibility: declaredAccessibility, + GetterAccessibility: getterAccessibility, + SetterAccessibility: setterAccessibility, + TypeName: typeName, + TypeNameWithNullabilityAnnotations: typeNameWithNullabilityAnnotations, + DefaultValue: defaultValue, + IsReferenceTypeOrUnconstraindTypeParameter: isReferenceTypeOrUnconstraindTypeParameter, + IsRequired: isRequired, + IsLocalCachingEnabled: isLocalCachingEnabled, + IsPropertyChangedCallbackImplemented: isPropertyChangedCallbackImplemented, + IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented, + IsNet8OrGreater: isNet8OrGreater); + }) + .WithTrackingName(WellKnownTrackingNames.Execute) + .Where(static item => item is not null)!; + + // Split and group by containing type + IncrementalValuesProvider> groupedPropertyInfo = + propertyInfo + .GroupBy( + keySelector: static item => item.Hierarchy, + elementSelector: static item => item, + resultSelector: static item => item.Values) + .WithTrackingName(WellKnownTrackingNames.Output); + + // Generate the source files, if any + context.RegisterSourceOutput(groupedPropertyInfo, static (context, item) => + { + using IndentedTextWriter writer = new(); + + item[0].Hierarchy.WriteSyntax( + state: item, + writer: writer, + baseTypes: [], + memberCallbacks: [Execute.WritePropertyDeclarations]); + + if (Execute.RequiresAdditionalTypes(item)) + { + writer.WriteLine(); + writer.WriteLine($"namespace {GeneratorName}"); + + using (writer.WriteBlock()) + { + Execute.WriteAdditionalTypes(item, writer); + } + } + + context.AddSource($"{item[0].Hierarchy.FullyQualifiedMetadataName}.g.cs", writer.ToString()); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs new file mode 100644 index 000000000..1206c2fef --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs @@ -0,0 +1,70 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error when a property with [GeneratedDependencyProperty] would generate conflicts. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyConflictingDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = [InvalidPropertyDeclarationWouldCauseConflicts]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + // Get the 'DependencyPropertyChangedEventArgs' symbol + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs) is not { } dependencyPropertyChangedEventArgsSymbol) + { + return; + } + + context.RegisterSymbolAction(context => + { + // Validate that we do have a property + if (context.Symbol is not IPropertySymbol propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Same logic as 'IsCandidateSymbolValid' in the generator + if (propertySymbol.Name == "Property") + { + // Check for collisions with the generated helpers and the property, only happens with these 2 types + if (propertySymbol.Type.SpecialType == SpecialType.System_Object || + SymbolEqualityComparer.Default.Equals(propertySymbol.Type, dependencyPropertyChangedEventArgsSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationWouldCauseConflicts, + attributeData.GetLocation(), + propertySymbol)); + } + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs new file mode 100644 index 000000000..b99a9f5f0 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs @@ -0,0 +1,65 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error when a property with [GeneratedDependencyProperty] is in an invalid type. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyContainingTypeDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = [InvalidPropertyDeclarationContainingTypeIsNotDependencyObject]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + // Get the 'DependencyObject' symbol + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject) is not { } dependencyObjectSymbol) + { + return; + } + + context.RegisterSymbolAction(context => + { + // Validate that we do have a property + if (context.Symbol is not IPropertySymbol propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Emit the diagnostic if the target is not valid + if (!propertySymbol.ContainingType.InheritsFromType(dependencyObjectSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationContainingTypeIsNotDependencyObject, + attributeData.GetLocation(), + propertySymbol)); + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs new file mode 100644 index 000000000..457c6a353 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs @@ -0,0 +1,128 @@ +// 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.Collections.Immutable; +using System.Threading; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used with an invalid default value type. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyDefaultValueTypeAttribute : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + InvalidPropertyDefaultValueNull, + InvalidPropertyDefaultValueType + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + context.RegisterSymbolAction(context => + { + // We're intentionally only looking for properties here + if (context.Symbol is not IPropertySymbol propertySymbol) + { + return; + } + + // If the property isn't using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Get the default value, if present (if it's not set, nothing to do) + if (!attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue)) + { + return; + } + + // Skip 'UnsetValue', that's special + + bool isNullableValueType = propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + bool isNullableType = !propertySymbol.Type.IsValueType || isNullableValueType; + + // Check for invalid 'null' default values + if (defaultValue.IsNull && !isNullableType) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDefaultValueNull, + attributeData.GetLocation(), + propertySymbol, + propertySymbol.Type)); + + return; + } + + + foreach (SyntaxReference propertyReference in propertySymbol.DeclaringSyntaxReferences) + { + SyntaxNode propertyNode = propertyReference.GetSyntax(context.CancellationToken); + + if (!IsValidPropertyDeclaration(propertyNode)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclaration, + attributeData.GetLocation(), + propertySymbol)); + + return; + } + } + }, SymbolKind.Property); + }); + } + + internal static bool IsDependencyPropertyUnsetValue( + AttributeData attributeData, + SemanticModel semanticModel, + CancellationToken token) + { + // If we do have a default value, we also want to check whether it's the special 'UnsetValue' placeholder. + // To do so, we get the application syntax, find the argument, then get the operation and inspect it. + if (attributeData.ApplicationSyntaxReference?.GetSyntax(token) is AttributeSyntax attributeSyntax) + { + foreach (AttributeArgumentSyntax attributeArgumentSyntax in attributeSyntax.ArgumentList?.Arguments ?? []) + { + // Let's see whether the current argument is the one that set the 'DefaultValue' property + if (attributeArgumentSyntax.NameEquals?.Name.Identifier.Text is "DefaultValue") + { + IOperation? operation = semanticModel.GetOperation(attributeArgumentSyntax.Expression, token); + + // Double check that it's a constant field reference (it could also be a literal of some kind, etc.) + if (operation is IFieldReferenceOperation { Field: { Name: "UnsetValue" } fieldSymbol }) + { + // Last step: we want to validate that the reference is actually to the special placeholder + if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName("CommunityToolkit.WinUI.GeneratedDependencyProperty")) + { + return true; + } + } + } + } + } + + return false; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs new file mode 100644 index 000000000..a7d99b6ff --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs @@ -0,0 +1,70 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates a warning when a property with [GeneratedDependencyProperty] would generate a nullability annotations violation. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyNonNullableDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = [NonNullablePropertyDeclarationIsNotEnforced]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + // Attempt to also get the '[MaybeNull]' symbols (there might be multiples, due to polyfills) + ImmutableArray maybeNullAttributeSymbol = context.Compilation.GetTypesByMetadataName("System.Diagnostics.CodeAnalysis.MaybeNullAttribute"); + + context.RegisterSymbolAction(context => + { + // Validate that we do have a property, and that it is of some type that can be explicitly nullable. + // We're intentionally ignoring 'Nullable' values here, since those are by defintiion nullable. + // Additionally, we only care about properties that are explicitly marked as not nullable. + // Lastly, we can skip required properties, since for those it's completely fine to be non-nullable. + if (context.Symbol is not IPropertySymbol { Type.IsValueType: false, NullableAnnotation: NullableAnnotation.NotAnnotated, IsRequired: false } propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // If the property has '[MaybeNull]', we never need to emit a diagnostic + if (propertySymbol.HasAttributeWithAnyType(maybeNullAttributeSymbol)) + { + return; + } + + // Emit a diagnostic if there is no default value, or if it's 'null' + if (!attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue) || defaultValue.IsNull) + { + context.ReportDiagnostic(Diagnostic.Create( + NonNullablePropertyDeclarationIsNotEnforced, + attributeData.GetLocation(), + propertySymbol)); + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs new file mode 100644 index 000000000..c82c02df0 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs @@ -0,0 +1,100 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used on an invalid property declaration. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertySymbolDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + InvalidPropertyDeclarationIsNotIncompletePartialDefinition, + InvalidPropertyDeclarationReturnsByRef, + InvalidPropertyDeclarationReturnsRefLikeType + ]; + + /// + public override void Initialize(AnalysisContext context) + { + // This generator is intentionally also analyzing generated code, because Roslyn will interpret properties + // that have '[GeneratedCode]' on them as being generated (and the same will apply to all partial parts). + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + // Get the '[GeneratedCode]' symbol + if (context.Compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute") is not { } generatedCodeAttributeSymbol) + { + return; + } + + context.RegisterSymbolAction(context => + { + // Ensure that we have some target property to analyze (also skip implementation parts) + if (context.Symbol is not IPropertySymbol { PartialDefinitionPart: null } propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Emit an error if the property is not a partial definition with no implementation... + if (propertySymbol is not { IsPartialDefinition: true, PartialImplementationPart: null }) + { + // ...But only if it wasn't actually generated by the [ObservableProperty] generator. + bool isImplementationAllowed = + propertySymbol is { IsPartialDefinition: true, PartialImplementationPart: IPropertySymbol implementationPartSymbol } && + implementationPartSymbol.TryGetAttributeWithType(generatedCodeAttributeSymbol, out AttributeData? generatedCodeAttributeData) && + generatedCodeAttributeData.TryGetConstructorArgument(0, out string? toolName) && + toolName == DependencyPropertyGenerator.GeneratorName; + + // Emit the diagnostic only for cases that were not valid generator outputs + if (!isImplementationAllowed) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationIsNotIncompletePartialDefinition, + attributeData.GetLocation(), + propertySymbol)); + } + } + + // Emit an error if the property returns a value by ref + if (propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationReturnsByRef, + attributeData.GetLocation(), + propertySymbol)); + } + + // Emit an error if the property type is a ref struct + if (propertySymbol.Type.IsRefLikeType) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationReturnsRefLikeType, + attributeData.GetLocation(), + propertySymbol)); + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs new file mode 100644 index 000000000..8b5d6b157 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs @@ -0,0 +1,102 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used on an invalid property declaration. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertySyntaxDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = [InvalidPropertyDeclaration]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + context.RegisterSymbolAction(context => + { + // We're intentionally only looking for properties here + if (context.Symbol is not IPropertySymbol propertySymbol) + { + return; + } + + // If the property isn't using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Check that the property has valid syntax + foreach (SyntaxReference propertyReference in propertySymbol.DeclaringSyntaxReferences) + { + SyntaxNode propertyNode = propertyReference.GetSyntax(context.CancellationToken); + + if (!IsValidPropertyDeclaration(propertyNode)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclaration, + attributeData.GetLocation(), + propertySymbol)); + + return; + } + } + }, SymbolKind.Property); + }); + } + + /// + /// Checks whether a given property declaration has valid syntax. + /// + /// The input node to validate. + /// Whether is a valid property. + internal static bool IsValidPropertyDeclaration(SyntaxNode node) + { + // The node must be a property declaration with two accessors + if (node is not PropertyDeclarationSyntax { AccessorList.Accessors: { Count: 2 } accessors, AttributeLists.Count: > 0 } property) + { + return false; + } + + // The property must be partial (we'll check that it's a declaration from its symbol) + if (!property.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return false; + } + + // Static properties are not supported + if (property.Modifiers.Any(SyntaxKind.StaticKeyword)) + { + return false; + } + + // The accessors must be a get and a set (with any accessibility) + if (accessors[0].Kind() is not (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration) || + accessors[1].Kind() is not (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration)) + { + return false; + } + + return true; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs new file mode 100644 index 000000000..200769544 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs @@ -0,0 +1,78 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error when using [GeneratedDependencyProperty] without the right C# version. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UnsupportedCSharpLanguageVersionAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + PropertyDeclarationRequiresCSharp13, + LocalCachingRequiresCSharpPreview + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // If we're using C# 'preview', we'll never emit any errors + if (context.Compilation.IsLanguageVersionPreview()) + { + return; + } + + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + + context.RegisterSymbolAction(context => + { + // Ensure that we have some target property to analyze (also skip implementation parts) + if (context.Symbol is not IPropertySymbol { PartialDefinitionPart: null } propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + bool isLocalCachingEnabled = attributeData.GetNamedArgument("IsLocalCacheEnabled", defaultValue: false); + + // Emit only up to one diagnostic, for whichever the highest required C# version would be + if (isLocalCachingEnabled && !context.Compilation.IsLanguageVersionPreview()) + { + context.ReportDiagnostic(Diagnostic.Create( + LocalCachingRequiresCSharpPreview, + attributeData.GetLocation(), + propertySymbol)); + } + else if (!isLocalCachingEnabled && !context.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp13)) + { + context.ReportDiagnostic(Diagnostic.Create( + PropertyDeclarationRequiresCSharp13, + attributeData.GetLocation(), + propertySymbol)); + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs new file mode 100644 index 000000000..213b3e8d7 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs @@ -0,0 +1,156 @@ +// 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; + +namespace CommunityToolkit.GeneratedDependencyProperty.Diagnostics; + +/// +/// A container for all instances for errors reported by analyzers in this project. +/// +internal static class DiagnosticDescriptors +{ + /// + /// "The property '{0}' cannot be used to generate a dependency property, as its declaration is not valid (it must be an instance (non static) partial property, with a getter and a setter that is not init-only)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclaration = new( + id: "WCTDP0001", + title: "Invalid property declaration for [GeneratedDependencyProperty]", + messageFormat: "The property '{0}' cannot be used to generate a dependency property, as its declaration is not valid (it must be an instance (non static) partial property, with a getter and a setter that is not init-only)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must be instance (non static) partial properties, with a getter and a setter that is not init-only.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' is not an incomplete partial definition ([ObservableProperty] must be used on a partial property definition with no implementation part)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationIsNotIncompletePartialDefinition = new( + id: "WCTDP0002", + title: "Using [GeneratedDependencyProperty] on an invalid partial property (not incomplete partial definition)", + messageFormat: """The property '{0}' is not an incomplete partial definition ([ObservableProperty] must be used on a partial property definition with no implementation part)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "A property using [GeneratedDependencyProperty] is not a partial implementation part ([GeneratedDependencyProperty] must be used on partial property definitions with no implementation part).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' cannot be used to generate a dependency property, as it returns a ref value ([GeneratedDependencyProperty] must be used on properties returning a non byref-like type by value)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationReturnsByRef = new( + id: "WCTDP0003", + title: "Using [GeneratedDependencyProperty] on a property that returns byref", + messageFormat: """The property '{0}' cannot be used to generate a dependency property, as it returns a ref value ([GeneratedDependencyProperty] must be used on properties returning a non byref-like type by value)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must not return a ref value (only reference types and non byref-like types are supported).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' cannot be used to generate a dependency property, as it returns a byref-like value ([GeneratedDependencyProperty] must be used on properties returning a non byref-like type by value)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationReturnsRefLikeType = new( + id: "WCTDP0004", + title: "Using [GeneratedDependencyProperty] on a property that returns byref-like", + messageFormat: """The property '{0}' cannot be used to generate a dependency property, as it returns a byref-like value ([GeneratedDependencyProperty] must be used on properties returning a non byref-like type by value)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must not return a byref-like value (only reference types and non byref-like types are supported).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' cannot be used to generate a dependency property, as its containing type doesn't inherit from DependencyObject". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationContainingTypeIsNotDependencyObject = new( + id: "WCTDP0005", + title: "Using [GeneratedDependencyProperty] on a property with invalid containing type", + messageFormat: "The property '{0}' cannot be used to generate a dependency property, as its containing type doesn't inherit from DependencyObject", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must be contained in a type that inherits from DependencyObject.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' cannot be used to generate a dependency property, as the project is not using C# 13 or greater (add 13.0 to your .csproj/.props file). + /// + public static readonly DiagnosticDescriptor PropertyDeclarationRequiresCSharp13 = new( + id: "WCTDP0006", + title: "Using [GeneratedDependencyProperty] requires C# 13", + messageFormat: "The property '{0}' cannot be used to generate a dependency property, as the project is not using C# 13 or greater (add 13.0 to your .csproj/.props file)", + category: typeof(UnsupportedCSharpLanguageVersionAnalyzer).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must be contained in a project using C# 13 or greater. Make sure to add 13.0 to your .csproj/.props file.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' cannot be used to generate a dependency property, as the project is not using C# 'preview', which is required when using the 'IsLocalCachingEnabled' option (add preview to your .csproj/.props file). + /// + public static readonly DiagnosticDescriptor LocalCachingRequiresCSharpPreview = new( + id: "WCTDP0007", + title: "Using [GeneratedDependencyProperty] with 'IsLocalCachingEnabled' requires C# 'preview'", + messageFormat: """The property '{0}' cannot be used to generate a dependency property, as the project is not using C# 'preview', which is required when using the 'IsLocalCachingEnabled' option (add preview to your .csproj/.props file)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] and using the 'IsLocalCachingEnabled' option must be contained in a project using C# 'preview'. Make sure to add preview to your .csproj/.props file.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' cannot be used to generate an dependency property, as its name or type would cause conflicts with other generated members ([GeneratedDependencyProperty] must not be used on properties named 'Property' of type either 'object' or 'DependencyPropertyChangedEventArgs'). + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationWouldCauseConflicts = new( + id: "WCTDP0008", + title: "Conflicting property declaration for [GeneratedDependencyProperty]", + messageFormat: "The property '{0}' cannot be used to generate an dependency property, as its name or type would cause conflicts with other generated members ([GeneratedDependencyProperty] must not be used on properties named 'Property' of type either 'object' or 'DependencyPropertyChangedEventArgs')", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must not be declared in such a way that would cause generate members to cause conflicts. In particular, they cannot be named 'Property' and be of type either 'object' or 'DependencyPropertyChangedEventArgs'.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' is not annotated as nullable, but it might contain a null value upon exiting the constructor (consider adding the 'required' modifier, setting a non-null default value if possible, or declaring the property as nullable). + /// + public static readonly DiagnosticDescriptor NonNullablePropertyDeclarationIsNotEnforced = new( + id: "WCTDP0009", + title: "Non-nullable dependency property is not guaranteed to not be null", + messageFormat: "The property '{0}' is not annotated as nullable, but it might contain a null value upon exiting the constructor (consider adding the 'required' modifier, setting a non-null default value if possible, or declaring the property as nullable)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Non-nullable properties annotated with [GeneratedDependencyProperty] should guarantee that their values will not be null upon exiting the constructor. This can be enforced by adding the 'required' modifier, setting a non-null default value if possible, or declaring the property as nullable.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' is declared with type '{1}', but 'DefaultValue' is set to 'null', which is not compatible (consider fixing the default value, or implementing the 'Get(ref object)' partial method to handle the type mismatch). + /// + public static readonly DiagnosticDescriptor InvalidPropertyDefaultValueNull = new( + id: "WCTDP0010", + title: "Invalid 'null' default value for [GeneratedDependencyProperty] use", + messageFormat: "The property '{0}' is declared with type '{1}', but 'DefaultValue' is set to 'null', which is not compatible (consider fixing the default value, or implementing the 'Get(ref object)' partial method to handle the type mismatch)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValue' should do so with an expression of a type comparible with the property type. Alternatively, the 'Get(ref object)' method should be implemented to handle the type mismatch.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// The property '{0}' is declared with type '{1}', but 'DefaultValue' is set to value '{2}' (type '{3}'), which is not compatible (consider fixing the default value, or implementing the 'Get(ref object)' partial method to handle the type mismatch). + /// + public static readonly DiagnosticDescriptor InvalidPropertyDefaultValueType = new( + id: "WCTDP0011", + title: "Invalid default value type for [GeneratedDependencyProperty] use", + messageFormat: "The property '{0}' is declared with type '{1}', but 'DefaultValue' is set to value '{2}' (type '{3}'), which is not compatible (consider fixing the default value, or implementing the 'Get(ref object)' partial method to handle the type mismatch)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValue' should do so with an expression of a type comparible with the property type. Alternatively, the 'Get(ref object)' method should be implemented to handle the type mismatch.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.cs new file mode 100644 index 000000000..413ecc2c7 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.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 Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class AccessibilityExtensions +{ + /// + /// Gets the expression for a given value. + /// + /// The input value. + /// The expression for . + public static string GetExpression(this Accessibility accessibility) + { + return accessibility switch + { + Accessibility.Private => "private", + Accessibility.ProtectedAndInternal => "private protected", + Accessibility.Protected => "protected", + Accessibility.Internal => "internal", + Accessibility.ProtectedOrInternal => "protected internal", + Accessibility.Public => "public", + _ => "" + }; + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs new file mode 100644 index 000000000..ef945b78b --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs @@ -0,0 +1,117 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class AttributeDataExtensions +{ + /// + /// Tries to get the location of the input instance. + /// + /// The input instance to get the location for. + /// The resulting location for , if a syntax reference is available. + public static Location? GetLocation(this AttributeData attributeData) + { + if (attributeData.ApplicationSyntaxReference is { } syntaxReference) + { + return syntaxReference.SyntaxTree.GetLocation(syntaxReference.Span); + } + + return null; + } + + /// + /// Tries to get a constructor argument at a given index from the input instance. + /// + /// The type of constructor argument to retrieve. + /// The target instance to get the argument from. + /// The index of the argument to try to retrieve. + /// The resulting argument, if it was found. + /// Whether or not an argument of type at position was found. + public static bool TryGetConstructorArgument(this AttributeData attributeData, int index, [NotNullWhen(true)] out T? result) + { + if (attributeData.ConstructorArguments.Length > index && + attributeData.ConstructorArguments[index].Value is T argument) + { + result = argument; + + return true; + } + + result = default; + + return false; + } + + /// + /// Tries to get a given named argument value from an instance, or a default value. + /// + /// The type of argument to check. + /// The target instance to check. + /// The name of the argument to check. + /// The default value to return if the argument is not found. + /// The argument value, or . + public static T? GetNamedArgument(this AttributeData attributeData, string name, T? defaultValue = default) + { + if (TryGetNamedArgument(attributeData, name, out T? value)) + { + return value; + } + + return defaultValue; + } + + /// + /// Tries to get a given named argument value from an instance, if present. + /// + /// The type of argument to check. + /// The target instance to check. + /// The name of the argument to check. + /// The resulting argument value, if present. + /// Whether or not contains an argument named with a valid value. + public static bool TryGetNamedArgument(this AttributeData attributeData, string name, out T? value) + { + if (TryGetNamedArgument(attributeData, name, out TypedConstant constantValue)) + { + value = (T?)constantValue.Value; + + return true; + } + + value = default; + + return false; + } + + /// + /// Tries to get a given named argument value from an instance, if present. + /// + /// The target instance to check. + /// The name of the argument to check. + /// The resulting argument value, if present. + /// Whether or not contains an argument named with a valid value. + public static bool TryGetNamedArgument(this AttributeData attributeData, string name, out TypedConstant value) + { + foreach (KeyValuePair argument in attributeData.NamedArguments) + { + if (argument.Key == name) + { + value = argument.Value; + + return true; + } + } + + value = default; + + return false; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs new file mode 100644 index 000000000..ff06af81c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs @@ -0,0 +1,45 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class CompilationExtensions +{ + /// + /// Checks whether a given compilation (assumed to be for C#) is using at least a given language version. + /// + /// The to consider for analysis. + /// The minimum language version to check. + /// Whether is using at least the specified language version. + public static bool HasLanguageVersionAtLeastEqualTo(this Compilation compilation, LanguageVersion languageVersion) + { + return ((CSharpCompilation)compilation).LanguageVersion >= languageVersion; + } + + /// + /// Checks whether a given compilation (assumed to be for C#) is using the preview language version. + /// + /// The to consider for analysis. + /// Whether is using the preview language version. + public static bool IsLanguageVersionPreview(this Compilation compilation) + { + return ((CSharpCompilation)compilation).LanguageVersion == LanguageVersion.Preview; + } + + /// + /// Gets whether the current target is a WinRT application (ie. legacy UWP). + /// + /// The input instance to inspect. + /// Whether the current target is a WinRT application. + public static bool IsWindowsRuntimeApplication(this Compilation compilation) + { + return compilation.Options.OutputKind == OutputKind.WindowsRuntimeApplication; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs new file mode 100644 index 000000000..c49e99c1c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs @@ -0,0 +1,94 @@ +// 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.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for types. +/// +internal static class ISymbolExtensions +{ + /// + /// Gets the fully qualified name for a given symbol (without nullability annotations). + /// + /// The input instance. + /// The fully qualified name for . + public static string GetFullyQualifiedName(this ISymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + /// + /// Gets the fully qualified name for a given symbol, including nullability annotations + /// + /// The input instance. + /// The fully qualified name for . + public static string GetFullyQualifiedNameWithNullabilityAnnotations(this ISymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)); + } + + /// + /// Checks whether or not a given symbol has an attribute with the specified type. + /// + /// The input instance to check. + /// The instance for the attribute type to look for. + /// Whether or not has an attribute with the specified type. + public static bool HasAttributeWithAnyType(this ISymbol symbol, ImmutableArray typeSymbols) + { + return TryGetAttributeWithAnyType(symbol, typeSymbols, out _); + } + + /// + /// Tries to get an attribute with the specified type. + /// + /// The input instance to check. + /// The instance for the attribute type to look for. + /// The resulting attribute, if it was found. + /// Whether or not has an attribute with the specified type. + public static bool TryGetAttributeWithType(this ISymbol symbol, ITypeSymbol typeSymbol, [NotNullWhen(true)] out AttributeData? attributeData) + { + foreach (AttributeData attribute in symbol.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, typeSymbol)) + { + attributeData = attribute; + + return true; + } + } + + attributeData = null; + + return false; + } + + /// + /// Tries to get an attribute with any of the specified types. + /// + /// The input instance to check. + /// The instance for the attribute type to look for. + /// The first attribute of a type matching any type in , if found. + /// Whether or not has an attribute with the specified type. + public static bool TryGetAttributeWithAnyType(this ISymbol symbol, ImmutableArray typeSymbols, [NotNullWhen(true)] out AttributeData? attributeData) + { + foreach (AttributeData attribute in symbol.GetAttributes()) + { + if (typeSymbols.Contains(attribute.AttributeClass!, SymbolEqualityComparer.Default)) + { + attributeData = attribute; + + return true; + } + } + + attributeData = null; + + return false; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs new file mode 100644 index 000000000..df8431653 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs @@ -0,0 +1,139 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for types. +/// +internal static class ITypeSymbolExtensions +{ + /// + /// Checks whether or not a given type symbol has a specified fully qualified metadata name. + /// + /// The input instance to check. + /// The full name to check. + /// Whether has a full name equals to . + public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string name) + { + using ImmutableArrayBuilder builder = new(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.WrittenSpan.SequenceEqual(name.AsSpan()); + } + + /// + /// Checks whether or not a given inherits from a specified type. + /// + /// The target instance to check. + /// The instance to check for inheritance from. + /// Whether or not inherits from . + public static bool InheritsFromType(this ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol) + { + INamedTypeSymbol? currentBaseTypeSymbol = typeSymbol.BaseType; + + while (currentBaseTypeSymbol is not null) + { + if (SymbolEqualityComparer.Default.Equals(currentBaseTypeSymbol, baseTypeSymbol)) + { + return true; + } + + currentBaseTypeSymbol = currentBaseTypeSymbol.BaseType; + } + + return false; + } + + /// + /// Checks whether or not a given inherits from a specified type. + /// + /// The target instance to check. + /// The full name of the type to check for inheritance. + /// Whether or not inherits from . + public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) + { + INamedTypeSymbol? baseType = typeSymbol.BaseType; + + while (baseType is not null) + { + if (baseType.HasFullyQualifiedMetadataName(name)) + { + return true; + } + + baseType = baseType.BaseType; + } + + return false; + } + + /// + /// Gets the fully qualified metadata name for a given instance. + /// + /// The input instance. + /// The fully qualified metadata name for . + public static string GetFullyQualifiedMetadataName(this ITypeSymbol symbol) + { + using ImmutableArrayBuilder builder = new(); + + symbol.AppendFullyQualifiedMetadataName(in builder); + + return builder.ToString(); + } + + /// + /// Appends the fully qualified metadata name for a given symbol to a target builder. + /// + /// The input instance. + /// The target instance. + public static void AppendFullyQualifiedMetadataName(this ITypeSymbol symbol, ref readonly ImmutableArrayBuilder builder) + { + static void BuildFrom(ISymbol? symbol, ref readonly ImmutableArrayBuilder builder) + { + switch (symbol) + { + // Namespaces that are nested also append a leading '.' + case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }: + BuildFrom(symbol.ContainingNamespace, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Other namespaces (ie. the one right before global) skip the leading '.' + case INamespaceSymbol { IsGlobalNamespace: false }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with no namespace just have their metadata name directly written + case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Types with a containing non-global namespace also append a leading '.' + case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }: + BuildFrom(namespaceSymbol, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Nested types append a leading '+' + case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }: + BuildFrom(typeSymbol, in builder); + builder.Add('+'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + default: + break; + } + } + + BuildFrom(symbol, in builder); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs new file mode 100644 index 000000000..4dde6f3a7 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs @@ -0,0 +1,71 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for . +/// +internal static class IncrementalValuesProviderExtensions +{ + /// + /// Groups items in a given sequence by a specified key. + /// + /// The type of value that this source provides access to. + /// The type of grouped key elements. + /// The type of projected elements. + /// The type of resulting items. + /// The input instance. + /// The key selection . + /// The element selection . + /// The result selection . + /// An with the grouped results. + public static IncrementalValuesProvider GroupBy( + this IncrementalValuesProvider source, + Func keySelector, + Func elementSelector, + Func<(TKey Key, EquatableArray Values), TResult> resultSelector) + where TValues : IEquatable + where TKey : IEquatable + where TElement : IEquatable + where TResult : IEquatable + { + return source.Collect().SelectMany((item, token) => + { + Dictionary.Builder> map = []; + + foreach (TValues value in item) + { + TKey key = keySelector(value); + TElement element = elementSelector(value); + + if (!map.TryGetValue(key, out ImmutableArray.Builder builder)) + { + builder = ImmutableArray.CreateBuilder(); + + map.Add(key, builder); + } + + builder.Add(element); + } + + token.ThrowIfCancellationRequested(); + + using ImmutableArrayBuilder result = new(); + + foreach (KeyValuePair.Builder> entry in map) + { + result.Add(resultSelector((entry.Key, entry.Value.ToImmutable()))); + } + + return result.ToImmutable(); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs new file mode 100644 index 000000000..688db2077 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs @@ -0,0 +1,104 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Helpers; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class IndentedTextWriterExtensions +{ + /// + /// Writes the following attributes into a target writer: + /// + /// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] + /// [global::System.Diagnostics.DebuggerNonUserCode] + /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + /// + /// + /// The instance to write into. + /// The name of the generator. + /// Whether to use fully qualified type names or not. + /// Whether to also include the attribute for non-user code. + public static void WriteGeneratedAttributes( + this IndentedTextWriter writer, + string generatorName, + bool useFullyQualifiedTypeNames = true, + bool includeNonUserCodeAttributes = true) + { + // We can use this class to get the assembly, as all files for generators are just included + // via shared projects. As such, the assembly will be the same as the generator type itself. + Version assemblyVersion = typeof(IndentedTextWriterExtensions).Assembly.GetName().Version; + + if (useFullyQualifiedTypeNames) + { + writer.WriteLine($$"""[global::System.CodeDom.Compiler.GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); + + if (includeNonUserCodeAttributes) + { + writer.WriteLine($$"""[global::System.Diagnostics.DebuggerNonUserCode]"""); + writer.WriteLine($$"""[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"""); + } + } + else + { + writer.WriteLine($$"""[GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); + + if (includeNonUserCodeAttributes) + { + writer.WriteLine($$"""[DebuggerNonUserCode]"""); + writer.WriteLine($$"""[ExcludeFromCodeCoverage]"""); + } + } + } + + /// + /// Writes a series of members separated by one line between each of them. + /// + /// The type of input items to process. + /// The instance to write into. + /// The input items to process. + /// The instance to invoke for each item. + public static void WriteLineSeparatedMembers( + this IndentedTextWriter writer, + ReadOnlySpan items, + IndentedTextWriter.Callback callback) + { + for (int i = 0; i < items.Length; i++) + { + if (i > 0) + { + writer.WriteLine(); + } + + callback(items[i], writer); + } + } + + /// + /// Writes a series of initialization expressions separated by a comma between each of them. + /// + /// The type of input items to process. + /// The instance to write into. + /// The input items to process. + /// The instance to invoke for each item. + public static void WriteInitializationExpressions( + this IndentedTextWriter writer, + ReadOnlySpan items, + IndentedTextWriter.Callback callback) + { + for (int i = 0; i < items.Length; i++) + { + callback(items[i], writer); + + if (i < items.Length - 1) + { + writer.WriteLine(","); + } + } + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 000000000..73c835beb --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,76 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// A with some extension methods for C# syntax nodes. +/// +internal static partial class SyntaxNodeExtensions +{ + /// + /// Checks whether a given is a given type declaration with or potentially with any base types, using only syntax. + /// + /// The type of declaration to check for. + /// The input to check. + /// Whether is a given type declaration with or potentially with any base types. + public static bool IsTypeDeclarationWithOrPotentiallyWithBaseTypes(this SyntaxNode node) + where T : TypeDeclarationSyntax + { + // Immediately bail if the node is not a type declaration of the specified type + if (node is not T typeDeclaration) + { + return false; + } + + // If the base types list is not empty, the type can definitely has implemented interfaces + if (typeDeclaration.BaseList is { Types.Count: > 0 }) + { + return true; + } + + // If the base types list is empty, check if the type is partial. If it is, it means + // that there could be another partial declaration with a non-empty base types list. + return typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword); + } + + /// + public static TNode? FirstAncestor(this SyntaxNode node, Func? predicate = null, bool ascendOutOfTrivia = true) + where TNode : SyntaxNode + { + // Helper method ported from 'SyntaxNode' + static SyntaxNode? GetParent(SyntaxNode node, bool ascendOutOfTrivia) + { + SyntaxNode? parent = node.Parent; + + if (parent is null && ascendOutOfTrivia) + { + if (node is IStructuredTriviaSyntax structuredTrivia) + { + parent = structuredTrivia.ParentTrivia.Token.Parent; + } + } + + return parent; + } + + // Traverse all parents and find the first one of the target type + for (SyntaxNode? parentNode = GetParent(node, ascendOutOfTrivia); + parentNode is not null; + parentNode = GetParent(parentNode, ascendOutOfTrivia)) + { + if (parentNode is TNode candidateNode && predicate?.Invoke(candidateNode) != false) + { + return candidateNode; + } + } + + return null; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs new file mode 100644 index 000000000..4a1d3605a --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs @@ -0,0 +1,216 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace CommunityToolkit.GeneratedDependencyProperty.Helpers; + +/// +/// Extensions for . +/// +internal static class EquatableArray +{ + /// + /// Creates an instance from a given . + /// + /// The type of items in the input array. + /// The input instance. + /// An instance from a given . + public static EquatableArray AsEquatableArray(this ImmutableArray array) + where T : IEquatable + { + return new(array); + } +} + +/// +/// An imutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +/// The input to wrap. +internal readonly struct EquatableArray(ImmutableArray array) : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + private readonly T[]? array = ImmutableCollectionsMarshal.AsArray(array); + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref AsImmutableArray().ItemRef(index); + } + + /// + /// Gets a value indicating whether the current array is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + /// + /// Gets a value indicating whether the current array is default or empty. + /// + public bool IsDefaultOrEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsDefaultOrEmpty; + } + + /// + /// Gets the length of the current array. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().Length; + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals(object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override unsafe int GetHashCode() + { + if (this.array is not T[] array) + { + return 0; + } + + HashCode hashCode = default; + + if (typeof(T) == typeof(byte)) + { + ReadOnlySpan span = array; + ref T r0 = ref MemoryMarshal.GetReference(span); + ref byte r1 = ref Unsafe.As(ref r0); + + fixed (byte* p = &r1) + { + ReadOnlySpan bytes = new(p, span.Length); + + hashCode.AddBytes(bytes); + } + } + else + { + foreach (T item in array) + { + hashCode.Add(item); + } + } + + return hashCode.ToHashCode(); + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return ImmutableCollectionsMarshal.AsImmutableArray(this.array); + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + /// + /// Copies the contents of this instance. to a mutable array. + /// + /// The newly instantiated array. + public T[] ToArray() + { + return [.. AsImmutableArray()]; + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) => FromImmutableArray(array); + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) => array.AsImmutableArray(); + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right); + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right); +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs new file mode 100644 index 000000000..8d9135cc7 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs @@ -0,0 +1,503 @@ +// 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.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +#pragma warning disable CS0809, IDE0009, IDE1006, IDE0048, CA1065 + +namespace System; + +/// +/// A polyfill type that mirrors some methods from on .7. +/// +internal struct HashCode +{ + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private static readonly uint seed = GenerateGlobalSeed(); + + private uint v1, v2, v3, v4; + private uint queue1, queue2, queue3; + private uint length; + + /// + /// Initializes the default seed. + /// + /// A random seed. + private static unsafe uint GenerateGlobalSeed() + { + byte[] bytes = new byte[4]; + + RandomNumberGenerator.Create().GetBytes(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + /// + /// Combines a value into a hash code. + /// + /// The type of the value to combine into the hash code. + /// The value to combine into the hash code. + /// The hash code that represents the value. + public static int Combine(T1 value) + { + uint hc1 = (uint)(value?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 4; + hash = QueueRound(hash, hc1); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines two values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 8; + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines three values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 12; + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines four values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 16; + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines five values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 20; + hash = QueueRound(hash, hc5); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines six values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 24; + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines seven values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The type of the seventh value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The seventh value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 28; + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines eight values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The type of the seventh value to combine into the hash code. + /// The type of the eighth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The seventh value to combine into the hash code. + /// The eighth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + uint hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 32; + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + public void Add(T value) + { + Add(value?.GetHashCode() ?? 0); + } + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + /// The instance to use. + public void Add(T value, IEqualityComparer? comparer) + { + Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); + } + + /// + /// Adds a span of bytes to the hash code. + /// + /// The span. + public void AddBytes(ReadOnlySpan value) + { + ref byte pos = ref MemoryMarshal.GetReference(value); + ref byte end = ref Unsafe.Add(ref pos, value.Length); + + while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int)) + { + Add(Unsafe.ReadUnaligned(ref pos)); + pos = ref Unsafe.Add(ref pos, sizeof(int)); + } + + while (Unsafe.IsAddressLessThan(ref pos, ref end)) + { + Add((int)pos); + pos = ref Unsafe.Add(ref pos, 1); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = seed + Prime1 + Prime2; + v2 = seed + Prime2; + v3 = seed; + v4 = seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixEmptyState() + { + return seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + + return hash; + } + + private void Add(int value) + { + uint val = (uint)value; + uint previousLength = length++; + uint position = previousLength % 4; + + if (position == 0) + { + queue1 = val; + } + else if (position == 1) + { + queue2 = val; + } + else if (position == 2) + { + queue3 = val; + } + else + { + if (previousLength == 3) + { + Initialize(out v1, out v2, out v3, out v4); + } + + v1 = Round(v1, queue1); + v2 = Round(v2, queue2); + v3 = Round(v3, queue3); + v4 = Round(v4, val); + } + } + + /// + /// Gets the resulting hashcode from the current instance. + /// + /// The resulting hashcode from the current instance. + public readonly int ToHashCode() + { + uint length = this.length; + uint position = length % 4; + uint hash = length < 4 ? MixEmptyState() : MixState(v1, v2, v3, v4); + + hash += length * 4; + + if (position > 0) + { + hash = QueueRound(hash, queue1); + + if (position > 1) + { + hash = QueueRound(hash, queue2); + + if (position > 2) + { + hash = QueueRound(hash, queue3); + } + } + } + + hash = MixFinal(hash); + + return (int)hash; + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + throw new NotSupportedException(); + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + { + throw new NotSupportedException(); + } + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return (value << offset) | (value >> (32 - offset)); + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs new file mode 100644 index 000000000..8238f6b1f --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs @@ -0,0 +1,365 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace CommunityToolkit.GeneratedDependencyProperty.Helpers; + +/// +/// A helper type to build sequences of values with pooled buffers. +/// +/// The type of items to create sequences for. +internal struct ImmutableArrayBuilder : IDisposable +{ + /// + /// The shared instance to share objects. + /// + private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); + + /// + /// The rented instance to use. + /// + private Writer? writer; + + /// + /// Creates a new object. + /// + public ImmutableArrayBuilder() + { + this.writer = SharedObjectPool.Allocate(); + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + public readonly ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.WrittenSpan; + } + + /// + /// Gets the number of elements currently written in the current instance. + /// + public readonly int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.Count; + } + + /// + /// Advances the current writer and gets a to the requested memory area. + /// + /// The requested size to advance by. + /// A to the requested memory area. + /// + /// No other data should be written to the builder while the returned + /// is in use, as it could invalidate the memory area wrapped by it, if resizing occurs. + /// + public readonly Span Advance(int requestedSize) + { + return this.writer!.Advance(requestedSize); + } + + /// + public readonly void Add(T item) + { + this.writer!.Add(item); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items to add at the end of the array. + public readonly void AddRange(ReadOnlySpan items) + { + this.writer!.AddRange(items); + } + + /// + public readonly void Clear() + { + this.writer!.Clear(); + } + + /// + /// Inserts an item to the builder at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the current instance. + public readonly void Insert(int index, T item) + { + this.writer!.Insert(index, item); + } + + /// + /// Gets an instance for the current builder. + /// + /// An instance for the current builder. + /// + /// The builder should not be mutated while an enumerator is in use. + /// + public readonly IEnumerable AsEnumerable() + { + return this.writer!; + } + + /// + public readonly ImmutableArray ToImmutable() + { + T[] array = this.writer!.WrittenSpan.ToArray(); + + return ImmutableCollectionsMarshal.AsImmutableArray(array); + } + + /// + public readonly T[] ToArray() + { + return this.writer!.WrittenSpan.ToArray(); + } + + /// + public override readonly string ToString() + { + return this.writer!.WrittenSpan.ToString(); + } + + /// + public void Dispose() + { + Writer? writer = this.writer; + + this.writer = null; + + if (writer is not null) + { + writer.Clear(); + + SharedObjectPool.Free(writer); + } + } + + /// + /// A class handling the actual buffer writing. + /// + private sealed class Writer : IList, IReadOnlyList + { + /// + /// The underlying array. + /// + private T[] array; + + /// + /// The starting offset within . + /// + private int index; + + /// + /// Creates a new instance with the specified parameters. + /// + public Writer() + { + if (typeof(T) == typeof(char)) + { + this.array = new T[1024]; + } + else + { + this.array = new T[8]; + } + + this.index = 0; + } + + /// + public int Count => this.index; + + /// + public ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.array, 0, this.index); + } + + /// + bool ICollection.IsReadOnly => true; + + /// + T IReadOnlyList.this[int index] => WrittenSpan[index]; + + /// + T IList.this[int index] + { + get => WrittenSpan[index]; + set => throw new NotSupportedException(); + } + + /// + public Span Advance(int requestedSize) + { + EnsureCapacity(requestedSize); + + Span span = this.array.AsSpan(this.index, requestedSize); + + this.index += requestedSize; + + return span; + } + + /// + public void Add(T value) + { + EnsureCapacity(1); + + this.array[this.index++] = value; + } + + /// + public void AddRange(ReadOnlySpan items) + { + EnsureCapacity(items.Length); + + items.CopyTo(this.array.AsSpan(this.index)); + + this.index += items.Length; + } + + /// + public void Clear(ReadOnlySpan items) + { + this.index = 0; + } + + /// + public void Insert(int index, T item) + { + if (index < 0 || index > this.index) + { + ImmutableArrayBuilder.ThrowArgumentOutOfRangeExceptionForIndex(); + } + + EnsureCapacity(1); + + if (index < this.index) + { + Array.Copy(this.array, index, this.array, index + 1, this.index - index); + } + + this.array[index] = item; + this.index++; + } + + /// + /// Clears the items in the current writer. + /// + public void Clear() + { + if (typeof(T) != typeof(byte) && + typeof(T) != typeof(char) && + typeof(T) != typeof(int)) + { + this.array.AsSpan(0, this.index).Clear(); + } + + this.index = 0; + } + + /// + /// Ensures that has enough free space to contain a given number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureCapacity(int requestedSize) + { + if (requestedSize > this.array.Length - this.index) + { + ResizeBuffer(requestedSize); + } + } + + /// + /// Resizes to ensure it can fit the specified number of new items. + /// + /// The minimum number of items to ensure space for in . + [MethodImpl(MethodImplOptions.NoInlining)] + private void ResizeBuffer(int sizeHint) + { + int minimumSize = this.index + sizeHint; + int requestedSize = Math.Max(this.array.Length * 2, minimumSize); + + T[] newArray = new T[requestedSize]; + + Array.Copy(this.array, newArray, this.index); + + this.array = newArray; + } + + /// + int IList.IndexOf(T item) + { + return Array.IndexOf(this.array, item, 0, this.index); + } + + /// + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + /// + bool ICollection.Contains(T item) + { + return Array.IndexOf(this.array, item, 0, this.index) >= 0; + } + + /// + void ICollection.CopyTo(T[] array, int arrayIndex) + { + Array.Copy(this.array, 0, array, arrayIndex, this.index); + } + + /// + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + T?[] array = this.array!; + int length = this.index; + + for (int i = 0; i < length; i++) + { + yield return array[i]!; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this).GetEnumerator(); + } + } +} + +/// +/// Private helpers for the type. +/// +file static class ImmutableArrayBuilder +{ + /// + /// Throws an for "index". + /// + public static void ThrowArgumentOutOfRangeExceptionForIndex() + { + throw new ArgumentOutOfRangeException("index"); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs new file mode 100644 index 000000000..9bfc74b65 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs @@ -0,0 +1,515 @@ +// 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.ComponentModel; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; + +#pragma warning disable IDE0290 + +namespace CommunityToolkit.GeneratedDependencyProperty.Helpers; + +/// +/// A helper type to build sequences of values with pooled buffers. +/// +internal sealed class IndentedTextWriter : IDisposable +{ + /// + /// The default indentation (4 spaces). + /// + private const string DefaultIndentation = " "; + + /// + /// The default new line ('\n'). + /// + private const char DefaultNewLine = '\n'; + + /// + /// The instance that text will be written to. + /// + private ImmutableArrayBuilder builder; + + /// + /// The current indentation level. + /// + private int currentIndentationLevel; + + /// + /// The current indentation, as text. + /// + private string currentIndentation = ""; + + /// + /// The cached array of available indentations, as text. + /// + private string[] availableIndentations; + + /// + /// Creates a new object. + /// + public IndentedTextWriter() + { + this.builder = new ImmutableArrayBuilder(); + this.currentIndentationLevel = 0; + this.currentIndentation = ""; + this.availableIndentations = new string[4]; + this.availableIndentations[0] = ""; + + for (int i = 1, n = this.availableIndentations.Length; i < n; i++) + { + this.availableIndentations[i] = this.availableIndentations[i - 1] + DefaultIndentation; + } + } + + /// + /// Advances the current writer and gets a to the requested memory area. + /// + /// The requested size to advance by. + /// A to the requested memory area. + /// + /// No other data should be written to the writer while the returned + /// is in use, as it could invalidate the memory area wrapped by it, if resizing occurs. + /// + public Span Advance(int requestedSize) + { + // Add the leading whitespace if needed (same as WriteRawText below) + if (this.builder.Count == 0 || this.builder.WrittenSpan[^1] == DefaultNewLine) + { + this.builder.AddRange(this.currentIndentation.AsSpan()); + } + + return this.builder.Advance(requestedSize); + } + + /// + /// Increases the current indentation level. + /// + public void IncreaseIndent() + { + this.currentIndentationLevel++; + + if (this.currentIndentationLevel == this.availableIndentations.Length) + { + Array.Resize(ref this.availableIndentations, this.availableIndentations.Length * 2); + } + + // Set both the current indentation and the current position in the indentations + // array to the expected indentation for the incremented level (ie. one level more). + this.currentIndentation = this.availableIndentations[this.currentIndentationLevel] + ??= this.availableIndentations[this.currentIndentationLevel - 1] + DefaultIndentation; + } + + /// + /// Decreases the current indentation level. + /// + public void DecreaseIndent() + { + this.currentIndentationLevel--; + this.currentIndentation = this.availableIndentations[this.currentIndentationLevel]; + } + + /// + /// Writes a block to the underlying buffer. + /// + /// A value to close the open block with. + public Block WriteBlock() + { + WriteLine("{"); + IncreaseIndent(); + + return new(this); + } + + /// + /// Writes content to the underlying buffer. + /// + /// The content to write. + /// Whether the input content is multiline. + public void Write(string content, bool isMultiline = false) + { + Write(content.AsSpan(), isMultiline); + } + + /// + /// Writes content to the underlying buffer. + /// + /// The content to write. + /// Whether the input content is multiline. + public void Write(ReadOnlySpan content, bool isMultiline = false) + { + if (isMultiline) + { + while (content.Length > 0) + { + int newLineIndex = content.IndexOf(DefaultNewLine); + + if (newLineIndex < 0) + { + // There are no new lines left, so the content can be written as a single line + WriteRawText(content); + + break; + } + else + { + ReadOnlySpan line = content[..newLineIndex]; + + // Write the current line (if it's empty, we can skip writing the text entirely). + // This ensures that raw multiline string literals with blank lines don't have + // extra whitespace at the start of those lines, which would otherwise happen. + WriteIf(!line.IsEmpty, line); + WriteLine(); + + // Move past the new line character (the result could be an empty span) + content = content[(newLineIndex + 1)..]; + } + } + } + else + { + WriteRawText(content); + } + } + + /// + /// Writes content to the underlying buffer. + /// + /// The interpolated string handler with content to write. + public void Write([InterpolatedStringHandlerArgument("")] ref WriteInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes content to the underlying buffer depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The content to write. + /// Whether the input content is multiline. + public void WriteIf(bool condition, string content, bool isMultiline = false) + { + if (condition) + { + Write(content.AsSpan(), isMultiline); + } + } + + /// + /// Writes content to the underlying buffer depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The content to write. + /// Whether the input content is multiline. + public void WriteIf(bool condition, ReadOnlySpan content, bool isMultiline = false) + { + if (condition) + { + Write(content, isMultiline); + } + } + + /// + /// Writes content to the underlying buffer depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The interpolated string handler with content to write. + public void WriteIf(bool condition, [InterpolatedStringHandlerArgument("", nameof(condition))] ref WriteIfInterpolatedStringHandler handler) + { + _ = this; + } + + /// + /// Writes a line to the underlying buffer. + /// + /// Indicates whether to skip adding the line if there already is one. + public void WriteLine(bool skipIfPresent = false) + { + if (skipIfPresent && this.builder.WrittenSpan is [.., '\n', '\n']) + { + return; + } + + this.builder.Add(DefaultNewLine); + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line. + /// + /// The content to write. + /// Whether the input content is multiline. + public void WriteLine(string content, bool isMultiline = false) + { + WriteLine(content.AsSpan(), isMultiline); + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line. + /// + /// The content to write. + /// Whether the input content is multiline. + public void WriteLine(ReadOnlySpan content, bool isMultiline = false) + { + Write(content, isMultiline); + WriteLine(); + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line. + /// + /// The interpolated string handler with content to write. + public void WriteLine([InterpolatedStringHandlerArgument("")] ref WriteInterpolatedStringHandler handler) + { + WriteLine(); + } + + /// + /// Writes a line to the underlying buffer depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// Indicates whether to skip adding the line if there already is one. + public void WriteLineIf(bool condition, bool skipIfPresent = false) + { + if (condition) + { + WriteLine(skipIfPresent); + } + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The content to write. + /// Whether the input content is multiline. + public void WriteLineIf(bool condition, string content, bool isMultiline = false) + { + if (condition) + { + WriteLine(content.AsSpan(), isMultiline); + } + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The content to write. + /// Whether the input content is multiline. + public void WriteLineIf(bool condition, ReadOnlySpan content, bool isMultiline = false) + { + if (condition) + { + Write(content, isMultiline); + WriteLine(); + } + } + + /// + /// Writes content to the underlying buffer and appends a trailing new line depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The interpolated string handler with content to write. + public void WriteLineIf(bool condition, [InterpolatedStringHandlerArgument("", nameof(condition))] ref WriteIfInterpolatedStringHandler handler) + { + if (condition) + { + WriteLine(); + } + } + + /// + public override string ToString() + { + return this.builder.WrittenSpan.Trim().ToString(); + } + + /// + public void Dispose() + { + this.builder.Dispose(); + } + + /// + /// Writes raw text to the underlying buffer, adding leading indentation if needed. + /// + /// The raw text to write. + private void WriteRawText(ReadOnlySpan content) + { + if (this.builder.Count == 0 || this.builder.WrittenSpan[^1] == DefaultNewLine) + { + this.builder.AddRange(this.currentIndentation.AsSpan()); + } + + this.builder.AddRange(content); + } + + /// + /// A delegate representing a callback to write data into an instance. + /// + /// The type of data to use. + /// The input data to use to write into . + /// The instance to write into. + public delegate void Callback(T value, IndentedTextWriter writer); + + /// + /// Represents an indented block that needs to be closed. + /// + /// The input instance to wrap. + public struct Block(IndentedTextWriter writer) : IDisposable + { + /// + /// The instance to write to. + /// + private IndentedTextWriter? writer = writer; + + /// + public void Dispose() + { + IndentedTextWriter? writer = this.writer; + + this.writer = null; + + if (writer is not null) + { + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + } + } + + /// + /// Provides a handler used by the language compiler to append interpolated strings into instances. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public readonly ref struct WriteInterpolatedStringHandler + { + /// The associated to which to append. + private readonly IndentedTextWriter writer; + + /// Creates a handler used to append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public WriteInterpolatedStringHandler(int literalLength, int formattedCount, IndentedTextWriter writer) + { + this.writer = writer; + } + + /// Writes the specified string to the handler. + /// The string to write. + public void AppendLiteral(string value) + { + this.writer.Write(value); + } + + /// Writes the specified value to the handler. + /// The value to write. + public void AppendFormatted(string? value) + { + AppendFormatted(value); + } + + /// Writes the specified character span to the handler. + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) + { + this.writer.Write(value); + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The type of the value to write. + public void AppendFormatted(T value) + { + if (value is not null) + { + this.writer.Write(value.ToString()); + } + } + + /// Writes the specified value to the handler. + /// The value to write. + /// The format string. + /// The type of the value to write. + public void AppendFormatted(T value, string? format) + { + if (value is IFormattable) + { + this.writer.Write(((IFormattable)value).ToString(format, CultureInfo.InvariantCulture)); + } + else if (value is not null) + { + this.writer.Write(value.ToString()); + } + } + } + + /// + /// Provides a handler used by the language compiler to conditionally append interpolated strings into instances. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [InterpolatedStringHandler] + public readonly ref struct WriteIfInterpolatedStringHandler + { + /// The associated to use. + private readonly WriteInterpolatedStringHandler handler; + + /// Creates a handler used to append an interpolated string into a . + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The associated to which to append. + /// The condition to use to decide whether or not to write content. + /// A value indicating whether formatting should proceed. + /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly. + public WriteIfInterpolatedStringHandler(int literalLength, int formattedCount, IndentedTextWriter writer, bool condition, out bool shouldAppend) + { + if (condition) + { + this.handler = new WriteInterpolatedStringHandler(literalLength, formattedCount, writer); + + shouldAppend = true; + } + else + { + this.handler = default; + + shouldAppend = false; + } + } + + /// + public void AppendLiteral(string value) + { + this.handler.AppendLiteral(value); + } + + /// + public void AppendFormatted(string? value) + { + this.handler.AppendFormatted(value); + } + + /// + public void AppendFormatted(ReadOnlySpan value) + { + this.handler.AppendFormatted(value); + } + + /// + public void AppendFormatted(T value) + { + this.handler.AppendFormatted(value); + } + + /// + public void AppendFormatted(T value, string? format) + { + this.handler.AppendFormatted(value, format); + } + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs new file mode 100644 index 000000000..723d0a73f --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs @@ -0,0 +1,154 @@ +// 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. + +// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +#pragma warning disable RS1035 + +namespace CommunityToolkit.GeneratedDependencyProperty.Helpers; + +/// +/// +/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose +/// is that limited number of frequently used objects can be kept in the pool for further recycling. +/// +/// +/// Notes: +/// +/// +/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there +/// is no space in the pool, extra returned objects will be dropped. +/// +/// +/// It is implied that if object was obtained from a pool, the caller will return it back in +/// a relatively short time. Keeping checked out objects for long durations is ok, but +/// reduces usefulness of pooling. Just new up your own. +/// +/// +/// +/// +/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. +/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new". +/// +/// +/// The type of objects to pool. +/// The input factory to produce items. +/// +/// The factory is stored for the lifetime of the pool. We will call this only when pool needs to +/// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()". +/// +/// The pool size to use. +internal sealed class ObjectPool(Func factory, int size) + where T : class +{ + /// + /// The array of cached items. + /// + private readonly Element[] items = new Element[size - 1]; + + /// + /// Storage for the pool objects. The first item is stored in a dedicated field + /// because we expect to be able to satisfy most requests from it. + /// + private T? firstItem; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The input factory to produce items. + public ObjectPool(Func factory) + : this(factory, Environment.ProcessorCount * 2) + { + } + + /// + /// Produces a instance. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Allocate() + { + T? item = this.firstItem; + + if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item)) + { + item = AllocateSlow(); + } + + return item; + } + + /// + /// Returns a given instance to the pool. + /// + /// The instance to return. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Free(T obj) + { + if (this.firstItem is null) + { + this.firstItem = obj; + } + else + { + FreeSlow(obj); + } + } + + /// + /// Allocates a new item. + /// + /// The returned item to use. + [MethodImpl(MethodImplOptions.NoInlining)] + private T AllocateSlow() + { + foreach (ref Element element in this.items.AsSpan()) + { + T? instance = element.Value; + + if (instance is not null) + { + if (instance == Interlocked.CompareExchange(ref element.Value, null, instance)) + { + return instance; + } + } + } + + return factory(); + } + + /// + /// Frees a given item. + /// + /// The item to return to the pool. + [MethodImpl(MethodImplOptions.NoInlining)] + private void FreeSlow(T obj) + { + foreach (ref Element element in this.items.AsSpan()) + { + if (element.Value is null) + { + element.Value = obj; + + break; + } + } + } + + /// + /// A container for a produced item (using a wrapper to avoid covariance checks). + /// + private struct Element + { + /// + /// The value held at the current element. + /// + internal T? Value; + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs new file mode 100644 index 000000000..3952dd95d --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs @@ -0,0 +1,40 @@ +// 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; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model representing a generated dependency property. +/// +/// The hierarchy info for the containing type. +/// The property name. +/// The accessibility of the property, if available. +/// The accessibility of the accessor, if available. +/// The accessibility of the accessor, if available. +/// The type name for the generated property (without nullability annotations). +/// The type name for the generated property, including nullability annotations. +/// The default value to set the generated property to. +/// Indicates whether the property is of a reference type or an unconstrained type parameter. +/// Whether or not the generated property should be marked as required. +/// Indicates whether local caching should be used for the property value. +/// Indicates whether the WinRT-based property changed callback is implemented. +/// Indicates whether the WinRT-based shared property changed callback is implemented. +/// Indicates whether the current target is .NET 8 or greater. +internal sealed record DependencyPropertyInfo( + HierarchyInfo Hierarchy, + string PropertyName, + Accessibility DeclaredAccessibility, + Accessibility GetterAccessibility, + Accessibility SetterAccessibility, + string TypeName, + string TypeNameWithNullabilityAnnotations, + TypedConstantInfo DefaultValue, + bool IsReferenceTypeOrUnconstraindTypeParameter, + bool IsRequired, + bool IsLocalCachingEnabled, + bool IsPropertyChangedCallbackImplemented, + bool IsSharedPropertyChangedCallbackImplemented, + bool IsNet8OrGreater); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs new file mode 100644 index 000000000..67393f1e9 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs @@ -0,0 +1,140 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; +using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model describing the hierarchy info for a specific type. +/// +/// The fully qualified metadata name for the current type. +/// Gets the namespace for the current type. +/// Gets the sequence of type definitions containing the current type. +internal sealed partial record HierarchyInfo(string FullyQualifiedMetadataName, string Namespace, EquatableArray Hierarchy) +{ + /// + /// Creates a new instance from a given . + /// + /// The input instance to gather info for. + /// A instance describing . + public static HierarchyInfo From(INamedTypeSymbol typeSymbol) + { + using ImmutableArrayBuilder hierarchy = new(); + + for (INamedTypeSymbol? parent = typeSymbol; + parent is not null; + parent = parent.ContainingType) + { + hierarchy.Add(new TypeInfo( + parent.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + parent.TypeKind, + parent.IsRecord)); + } + + return new( + typeSymbol.GetFullyQualifiedMetadataName(), + typeSymbol.ContainingNamespace.ToDisplayString(new(typeQualificationStyle: NameAndContainingTypesAndNamespaces)), + hierarchy.ToImmutable()); + } + + /// + /// Writes syntax for the current hierarchy into a target writer. + /// + /// The type of state to pass to callbacks. + /// The input state to pass to callbacks. + /// The target instance to write text to. + /// A list of base types to add to the generated type, if any. + /// The callbacks to use to write members into the declared type. + public void WriteSyntax( + T state, + IndentedTextWriter writer, + ReadOnlySpan baseTypes, + ReadOnlySpan> memberCallbacks) + { + // Write the generated file header + writer.WriteLine("// "); + writer.WriteLine("#pragma warning disable"); + writer.WriteLine("#nullable enable"); + writer.WriteLine(); + + // Declare the namespace, if needed + if (Namespace.Length > 0) + { + writer.WriteLine($"namespace {Namespace}"); + writer.WriteLine("{"); + writer.IncreaseIndent(); + } + + // Declare all the opening types until the inner-most one + for (int i = Hierarchy.Length - 1; i >= 0; i--) + { + writer.WriteLine($$"""/// """); + writer.Write($$"""partial {{Hierarchy[i].GetTypeKeyword()}} {{Hierarchy[i].QualifiedName}}"""); + + // Add any base types, if needed + if (i == 0 && !baseTypes.IsEmpty) + { + writer.Write(" : "); + writer.WriteInitializationExpressions(baseTypes, static (item, writer) => writer.Write(item)); + writer.WriteLine(); + } + else + { + writer.WriteLine(); + } + + writer.WriteLine($$"""{"""); + writer.IncreaseIndent(); + } + + // Generate all nested members + writer.WriteLineSeparatedMembers(memberCallbacks, (callback, writer) => callback(state, writer)); + + // Close all scopes and reduce the indentation + for (int i = 0; i < Hierarchy.Length; i++) + { + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + + // Close the namespace scope as well, if needed + if (Namespace.Length > 0) + { + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + } + + /// + /// Gets the fully qualified type name for the current instance. + /// + /// The fully qualified type name for the current instance. + public string GetFullyQualifiedTypeName() + { + using ImmutableArrayBuilder fullyQualifiedTypeName = new(); + + fullyQualifiedTypeName.AddRange("global::".AsSpan()); + + if (Namespace.Length > 0) + { + fullyQualifiedTypeName.AddRange(Namespace.AsSpan()); + fullyQualifiedTypeName.Add('.'); + } + + fullyQualifiedTypeName.AddRange(Hierarchy[^1].QualifiedName.AsSpan()); + + for (int i = Hierarchy.Length - 2; i >= 0; i--) + { + fullyQualifiedTypeName.Add('.'); + fullyQualifiedTypeName.AddRange(Hierarchy[i].QualifiedName.AsSpan()); + } + + return fullyQualifiedTypeName.ToString(); + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.cs new file mode 100644 index 000000000..a1b5e7e02 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.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 Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model describing a type info in a type hierarchy. +/// +/// The qualified name for the type. +/// The type of the type in the hierarchy. +/// Whether the type is a record type. +internal sealed record TypeInfo(string QualifiedName, TypeKind Kind, bool IsRecord) +{ + /// + /// Gets the keyword for the current type kind. + /// + /// The keyword for the current type kind. + public string GetTypeKeyword() + { + return Kind switch + { + TypeKind.Struct when IsRecord => "record struct", + TypeKind.Struct => "struct", + TypeKind.Interface => "interface", + TypeKind.Class when IsRecord => "record", + _ => "class" + }; + } +} \ No newline at end of file diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs new file mode 100644 index 000000000..bb030884c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs @@ -0,0 +1,60 @@ +// 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.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +partial record TypedConstantInfo +{ + /// + /// Creates a new instance from a given value. + /// + /// The input value. + /// A instance representing . + /// Thrown if the input argument is not valid. + public static TypedConstantInfo Create(TypedConstant arg) + { + if (arg.IsNull) + { + return new Null(); + } + + if (arg.Kind == TypedConstantKind.Array) + { + string elementTypeName = ((IArrayTypeSymbol)arg.Type!).ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + ImmutableArray items = arg.Values.Select(Create).ToImmutableArray(); + + return new Array(elementTypeName, items); + } + + return (arg.Kind, arg.Value) switch + { + (TypedConstantKind.Primitive, string text) => new Primitive.String(text), + (TypedConstantKind.Primitive, bool flag) => new Primitive.Boolean(flag), + (TypedConstantKind.Primitive, object value) => value switch + { + byte b => new Primitive.Of(b), + char c => new Primitive.Of(c), + double d => new Primitive.Of(d), + float f => new Primitive.Of(f), + int i => new Primitive.Of(i), + long l => new Primitive.Of(l), + sbyte sb => new Primitive.Of(sb), + short sh => new Primitive.Of(sh), + uint ui => new Primitive.Of(ui), + ulong ul => new Primitive.Of(ul), + ushort ush => new Primitive.Of(ush), + _ => throw new ArgumentException("Invalid primitive type") + }, + (TypedConstantKind.Type, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), + (TypedConstantKind.Enum, object value) => new Enum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), + _ => throw new ArgumentException("Invalid typed constant type"), + }; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs new file mode 100644 index 000000000..d06ef9618 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs @@ -0,0 +1,187 @@ +// 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.Globalization; +using System.Linq; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model representing a typed constant item. +/// +/// This model is fully serializable and comparable. +internal abstract partial record TypedConstantInfo +{ + /// + /// A type representing an array. + /// + /// The type name for array elements. + /// The sequence of contained elements. + public sealed record Array(string ElementTypeName, EquatableArray Items) : TypedConstantInfo + { + /// + public override string ToString() + { + ArrayCreationExpressionSyntax arrayCreationExpressionSyntax = + ArrayCreationExpression( + ArrayType(IdentifierName(ElementTypeName)) + .AddRankSpecifiers(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression())))) + .WithInitializer(InitializerExpression(SyntaxKind.ArrayInitializerExpression) + .AddExpressions(Items.Select(static c => ParseExpression(c.ToString())).ToArray())); + + return arrayCreationExpressionSyntax.NormalizeWhitespace(eol: "\n").ToFullString(); + } + } + + /// + /// A type representing a primitive value. + /// + public abstract record Primitive : TypedConstantInfo + { + /// + /// A type representing a value. + /// + /// The input value. + public sealed record String(string Value) : TypedConstantInfo + { + /// + public override string ToString() + { + return '"' + Value + '"'; + } + } + + /// + /// A type representing a value. + /// + /// The input value. + public sealed record Boolean(bool Value) : TypedConstantInfo + { + /// + public override string ToString() + { + return Value ? "true" : "false"; + } + } + + /// + /// A type representing a generic primitive value. + /// + /// The primitive type. + /// The input primitive value. + public sealed record Of(T Value) : TypedConstantInfo + where T : unmanaged, IEquatable + { + /// + public override string ToString() + { + LiteralExpressionSyntax expressionSyntax = LiteralExpression(SyntaxKind.NumericLiteralExpression, Value switch + { + byte b => Literal(b), + char c => Literal(c), + + // For doubles, we need to manually format it and always add the trailing "D" suffix. + // This ensures that the correct type is produced if the expression was assigned to + // an object (eg. the literal was used in an attribute object parameter/property). + double d => Literal(d.ToString("R", CultureInfo.InvariantCulture) + "D", d), + + // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed + float f => Literal(f), + int i => Literal(i), + long l => Literal(l), + sbyte sb => Literal(sb), + short sh => Literal(sh), + uint ui => Literal(ui), + ulong ul => Literal(ul), + ushort ush => Literal(ush), + _ => throw new ArgumentException("Invalid primitive type") + }); + + return expressionSyntax.NormalizeWhitespace(eol: "\n").ToFullString(); + } + } + } + + /// + /// A type representing a type. + /// + /// The input type name. + public sealed record Type(string TypeName) : TypedConstantInfo + { + /// + public override string ToString() + { + return $"typeof({TypeName})"; + } + } + + /// + /// A type representing an enum value. + /// + /// The enum type name. + /// The boxed enum value. + public sealed record Enum(string TypeName, object Value) : TypedConstantInfo + { + /// + public override string ToString() + { + // We let Roslyn parse the value expression, so that it can automatically handle both positive and negative values. This + // is needed because negative values have a different syntax tree (UnaryMinusExpression holding the numeric expression). + ExpressionSyntax valueExpression = ParseExpression(Value.ToString()); + + // If the value is negative, we have to put parentheses around them (to avoid CS0075 errors) + if (valueExpression is PrefixUnaryExpressionSyntax unaryExpression && unaryExpression.IsKind(SyntaxKind.UnaryMinusExpression)) + { + valueExpression = ParenthesizedExpression(valueExpression); + } + + // Now we can safely return the cast expression for the target enum type (with optional parentheses if needed) + return $"({TypeName}){valueExpression.NormalizeWhitespace(eol: "\n").ToFullString()}"; + } + } + + /// + /// A type representing a value. + /// + public sealed record Null : TypedConstantInfo + { + /// + public override string ToString() + { + return "null"; + } + } + + /// + /// A type representing default value for a specific type. + /// + /// The input type name. + public sealed record Default(string TypeName) : TypedConstantInfo + { + /// + public override string ToString() + { + return $"default({TypeName})"; + } + } + + /// + /// A type representing the special unset value. + /// + public sealed record UnsetValue : TypedConstantInfo + { + /// + public override string ToString() + { + return $"global::{WellKnownTypeNames.DependencyProperty}.UnsetValue"; + } + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj new file mode 100644 index 000000000..ca2d08512 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj @@ -0,0 +1,41 @@ + + + net8.0-windows10.0.17763.0 + true + false + + + $(NoWarn);NU1903 + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs new file mode 100644 index 000000000..10478a2bc --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs @@ -0,0 +1,60 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using CommunityToolkit.WinUI; + +namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers; + +/// +/// A custom that uses a specific C# language version to parse code. +/// +/// The type of the analyzer to test. +internal sealed class CSharpAnalyzerTest : CSharpAnalyzerTest + where TAnalyzer : DiagnosticAnalyzer, 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. + private CSharpAnalyzerTest(LanguageVersion languageVersion) + { + this.languageVersion = languageVersion; + } + + /// + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(this.languageVersion, DocumentationMode.Diagnose); + } + + /// + /// The language version to use to run the test. + public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion, params DiagnosticResult[] expected) + { + CSharpAnalyzerTest test = new(languageVersion) { TestCode = source }; + + test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location)); + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); + + test.ExpectedDiagnostics.AddRange(expected); + + return test.RunAsync(CancellationToken.None); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs new file mode 100644 index 000000000..03d746354 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -0,0 +1,220 @@ +// 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.IO; +using System.Linq; +using Basic.Reference.Assemblies; +using CommunityToolkit.WinUI; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; + +/// +/// A helper type to run source generator tests. +/// +/// The type of generator to test. +internal static class CSharpGeneratorTest + where TGenerator : IIncrementalGenerator, new() +{ + /// + /// Verifies the resulting diagnostics from a source generator. + /// + /// The input source to process. + /// The expected diagnostics ids to be generated. + public static void VerifyDiagnostics(string source, params string[] diagnosticsIds) + { + RunGenerator(source, out Compilation compilation, out ImmutableArray diagnostics); + + Dictionary diagnosticMap = diagnostics.DistinctBy(diagnostic => diagnostic.Id).ToDictionary(diagnostic => diagnostic.Id); + + // Check that the diagnostics match + Assert.IsTrue(diagnosticMap.Keys.ToHashSet().SetEquals(diagnosticsIds), $"Diagnostics didn't match. {string.Join(", ", diagnosticMap.Values)}"); + + // If the compilation was supposed to succeed, ensure that no further errors were generated + if (diagnosticsIds.Length == 0) + { + // Compute diagnostics for the final compiled output (just include errors) + List outputCompilationDiagnostics = compilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToList(); + + Assert.IsTrue(outputCompilationDiagnostics.Count == 0, $"resultingIds: {string.Join(", ", outputCompilationDiagnostics)}"); + } + } + + /// + /// Verifies the resulting sources produced by a source generator. + /// + /// The input source to process. + /// The expected source to be generated. + /// The language version to use to run the test. + public static void VerifySources(string source, (string Filename, string Source) result, LanguageVersion languageVersion = LanguageVersion.CSharp12) + { + RunGenerator(source, out Compilation compilation, out ImmutableArray diagnostics, languageVersion); + + // Ensure that no diagnostics were generated + CollectionAssert.AreEquivalent(Array.Empty(), diagnostics); + + // Update the assembly version using the version from the assembly of the input generators. + // This allows the tests to not need updates whenever the version of the MVVM Toolkit changes. + string expectedText = result.Source.Replace("", $"\"{typeof(TGenerator).Assembly.GetName().Version}\""); + string actualText = compilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == result.Filename).ToString(); + + Assert.AreEqual(expectedText, actualText); + } + + /// + /// Verifies the incremental generator steps for a given source generator. + /// + /// The input source to process. + /// The updated source to process. + /// The reason for the first "Execute" step. + /// The reason for the "Diagnostics" step. + /// The reason for the "Output" step. + /// The reason for the output step for the diagnostics. + /// The reason for the final output source. + /// The language version to use to run the test. + public static void VerifyIncrementalSteps( + string source, + string updatedSource, + IncrementalStepRunReason executeReason, + IncrementalStepRunReason? diagnosticsReason, + IncrementalStepRunReason outputReason, + IncrementalStepRunReason? diagnosticsSourceReason, + IncrementalStepRunReason sourceReason, + LanguageVersion languageVersion = LanguageVersion.CSharp12) + { + Compilation compilation = CreateCompilation(source, languageVersion); + + GeneratorDriver driver = CSharpGeneratorDriver.Create( + generators: [new TGenerator().AsSourceGenerator()], + driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true)); + + // Run the generator on the initial sources + driver = driver.RunGenerators(compilation); + + // Update the compilation by replacing the source + compilation = compilation.ReplaceSyntaxTree( + compilation.SyntaxTrees.First(), + CSharpSyntaxTree.ParseText(updatedSource, CSharpParseOptions.Default.WithLanguageVersion(languageVersion))); + + // Run the generators again on the updated source + driver = driver.RunGenerators(compilation); + + GeneratorRunResult result = driver.GetRunResult().Results.Single(); + + // Get the generated sources and validate them. We have two possible cases: if no diagnostics + // are produced, then just the output source node is triggered. Otherwise, we'll also have one + // output node which is used to emit the gathered diagnostics from the initial transform step. + if (diagnosticsSourceReason is not null) + { + Assert.AreEqual( + expected: 2, + actual: + result.TrackedOutputSteps + .SelectMany(outputStep => outputStep.Value) + .SelectMany(output => output.Outputs) + .Count()); + + // The "Diagnostics" name has one more parent compared to "Output", because it also + // has one extra Where(...) call on the node (used to filter out empty diagnostics). + Assert.AreEqual( + expected: diagnosticsSourceReason, + actual: + result.TrackedOutputSteps + .Single().Value + .Single(run => run.Inputs[0].Source.Inputs[0].Source.Name == "Diagnostics") + .Outputs.Single().Reason); + + Assert.AreEqual( + expected: sourceReason, + actual: + result.TrackedOutputSteps + .Single().Value + .Single(run => run.Inputs[0].Source.Name == "Output") + .Outputs.Single().Reason); + } + else + { + (object Value, IncrementalStepRunReason Reason)[] sourceOuputs = + result.TrackedOutputSteps + .SelectMany(outputStep => outputStep.Value) + .SelectMany(output => output.Outputs) + .ToArray(); + + Assert.AreEqual(1, sourceOuputs.Length); + Assert.AreEqual(sourceReason, sourceOuputs[0].Reason); + } + + Assert.AreEqual(executeReason, result.TrackedSteps["Execute"].Single().Outputs[0].Reason); + Assert.AreEqual(outputReason, result.TrackedSteps["Output"].Single().Outputs[0].Reason); + + // Check the diagnostics reason, which might not be present + if (diagnosticsReason is not null) + { + Assert.AreEqual(diagnosticsReason, result.TrackedSteps["Diagnostics"].Single().Outputs[0].Reason); + } + else + { + Assert.IsFalse(result.TrackedSteps.ContainsKey("Diagnostics")); + } + } + + /// + /// Creates a compilation from a given source. + /// + /// The input source to process. + /// The language version to use to run the test. + /// The resulting object. + private static CSharpCompilation CreateCompilation(string source, LanguageVersion languageVersion = LanguageVersion.CSharp12) + { + // Get all assembly references for the .NET TFM and ComputeSharp + IEnumerable metadataReferences = + [ + .. Net80.References.All, + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + ]; + + // Parse the source text + SyntaxTree sourceTree = CSharpSyntaxTree.ParseText( + source, + CSharpParseOptions.Default.WithLanguageVersion(languageVersion)); + + // Create the original compilation + return CSharpCompilation.Create( + "original", + [sourceTree], + metadataReferences, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); + } + + /// + /// Runs a generator and gathers the output results. + /// + /// The input source to process. + /// + /// + /// The language version to use to run the test. + private static void RunGenerator( + string source, + out Compilation compilation, + out ImmutableArray diagnostics, + LanguageVersion languageVersion = LanguageVersion.CSharp12) + { + Compilation originalCompilation = CreateCompilation(source, languageVersion); + + // Create the generator driver with the D2D shader generator + GeneratorDriver driver = CSharpGeneratorDriver.Create(new TGenerator()).WithUpdatedParseOptions(originalCompilation.SyntaxTrees.First().Options); + + // Run all source generators on the input source code + _ = driver.RunGeneratorsAndUpdateCompilation(originalCompilation, out compilation, out diagnostics); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs new file mode 100644 index 000000000..35164ff21 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs @@ -0,0 +1,746 @@ +// 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.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +[TestClass] +public class Test_Analyzers +{ + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_ValidAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_NotPartial_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0001:GeneratedDependencyProperty|}] + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_NoSetter_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0001:GeneratedDependencyProperty|}] + public partial string? {|CS9248:Name|} { get; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_NoGetter_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0001:GeneratedDependencyProperty|}] + public partial string? {|CS9248:Name|} { set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_InitOnlySetter_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0001:GeneratedDependencyProperty|}] + public partial string? {|CS9248:Name|} { get; init; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySyntaxDeclarationAnalyzer_Static_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0001:GeneratedDependencyProperty|}] + public static partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ValidAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_OnUnannotatedPartialPropertyWithImplementation_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + public partial string? Name { get; set; } + + public partial string? Name + { + get => field; + set { } + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.Preview); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_OnImplementedProperty_GeneratedByToolkit_DoesNotWarn() + { + const string source = """ + using System.CodeDom.Compiler; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string Name { get; set; } + + [GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", "1.0.0")] + public partial string Name + { + get => field; + set { } + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.Preview); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_OnImplementedProperty_GeneratedByAnotherGenerator_Warns() + { + const string source = """ + using System.CodeDom.Compiler; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0002:GeneratedDependencyProperty|}] + public partial string Name { get; set; } + + [GeneratedCode("Some.Other.Generator", "1.0.0")] + public partial string Name + { + get => field; + set { } + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.Preview); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_OnImplementedProperty_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0002:GeneratedDependencyProperty|}] + public partial string Name { get; set; } + + public partial string Name + { + get => field; + set { } + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.Preview); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ReturnsRef_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0003:GeneratedDependencyProperty|}] + public partial ref int {|CS9248:Name|} { get; {|CS8147:set|}; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ReturnsRefReadOnly_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0003:GeneratedDependencyProperty|}] + public partial ref readonly int {|CS9248:Name|} { get; {|CS8147:set|}; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ReturnsByRefLike_Warns() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0004:GeneratedDependencyProperty|}] + public partial Span {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyContainingTypeDeclarationAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl + { + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyContainingTypeDeclarationAnalyzer_ValidType1_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyContainingTypeDeclarationAnalyzer_ValidType2_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyContainingTypeDeclarationAnalyzer_InvalidType_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + + namespace MyApp; + + public partial class MyControl + { + [{|WCTDP0005:GeneratedDependencyProperty|}] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task UnsupportedCSharpLanguageVersionAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp10); + } + + [TestMethod] + [DataRow(LanguageVersion.CSharp13)] + [DataRow(LanguageVersion.Preview)] + public async Task UnsupportedCSharpLanguageVersionAnalyzer_ValidLanguageVersion_DoesNotWarn(LanguageVersion languageVersion) + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, languageVersion); + } + + [TestMethod] + [DataRow(LanguageVersion.CSharp10)] + [DataRow(LanguageVersion.CSharp12)] + public async Task UnsupportedCSharpLanguageVersionAnalyzer_RequiresCSharp13_Warns(LanguageVersion languageVersion) + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0006:GeneratedDependencyProperty|}] + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, languageVersion); + } + + [TestMethod] + [DataRow(LanguageVersion.CSharp10)] + [DataRow(LanguageVersion.CSharp13)] + public async Task UnsupportedCSharpLanguageVersionAnalyzer_CSharp10_RequiresPreview_Warns(LanguageVersion languageVersion) + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0007:GeneratedDependencyProperty(IsLocalCacheEnabled = true)|}] + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, languageVersion); + } + + [TestMethod] + public async Task InvalidPropertyConflictingDeclarationAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyConflictingDeclarationAnalyzer_ValidProperty_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("object")] + [DataRow("DependencyPropertyChangedEventArgs")] + public async Task InvalidPropertyConflictingDeclarationAnalyzer_InvalidPropertyType_ValidName_DoesNotWarn(string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("object")] + [DataRow("DependencyPropertyChangedEventArgs")] + public async Task InvalidPropertyConflictingDeclarationAnalyzer_InvalidPropertyType_NamedProperty_Warns(string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0008:GeneratedDependencyProperty|}] + public partial {{propertyType}} {|CS9248:Property|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public class MyControl : Control + { + public string Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("int")] + [DataRow("int?")] + [DataRow("string?")] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NullableOrNotApplicableType_DoesNotWarn(string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithMaybeNullAttribute_DoesNotWarn() + { + string source = $$""" + using System.Diagnostics.CodeAnalysis; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + [MaybeNull] + public partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_Required_DoesNotWarn() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public required partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_NullableDisabled_DoesNotWarn() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public required partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithNonNullDefaultValue_DoesNotWarn() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = "Bob")] + public required partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_Warns() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0009:GeneratedDependencyProperty|}] + public partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithNullDefaultValue_Warns() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0009:GeneratedDependencyProperty(DefaultValue = null)|}] + public partial string {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs new file mode 100644 index 000000000..66777d619 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs @@ -0,0 +1,48 @@ +// 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.IO; +using System.Reflection; +using CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +partial class Test_DependencyPropertyGenerator +{ + [TestMethod] + [DataRow("GeneratedDependencyProperty")] + [DataRow("GeneratedDependencyPropertyAttribute")] + public void SingleProperty_String_WithNoCaching_PostInitializationSources(string typeName) + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? Name { get; set; } + } + """; + + string fileName = $"{typeName}.g.cs"; + string sourceText; + + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fileName)) + using (StreamReader reader = new(stream)) + { + sourceText = reader.ReadToEnd(); + } + + string updatedSourceText = sourceText + .Replace("", "CommunityToolkit.WinUI.DependencyPropertyGenerator") + .Replace("", typeof(DependencyPropertyGenerator).Assembly.GetName().Version.ToString()); + + CSharpGeneratorTest.VerifySources(source, (fileName, updatedSourceText), languageVersion: LanguageVersion.CSharp13); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs new file mode 100644 index 000000000..76587fa83 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs @@ -0,0 +1,1962 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +[TestClass] +public partial class Test_DependencyPropertyGenerator +{ + [TestMethod] + public void SingleProperty_Int32_WithLocalCache() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(IsLocalCacheEnabled = true)] + public partial int Number { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int))); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get => field; + set + { + OnNumberSet(ref value); + + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + return; + } + + int __oldValue = field; + + OnNumberChanging(value); + OnNumberChanging(__oldValue, value); + + field = value; + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + OnNumberChanged(__oldValue, value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int newValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithLocalCache_WithCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(IsLocalCacheEnabled = true)] + public partial int Number { get; set; } + + partial void OnNumberPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get => field; + set + { + OnNumberSet(ref value); + + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + return; + } + + int __oldValue = field; + + OnNumberChanging(value); + OnNumberChanging(__oldValue, value); + + field = value; + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + OnNumberChanged(__oldValue, value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int newValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file static class PropertyChangedCallbacks + { + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback Number() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + OnNumberPropertyChanged(__this, e); + } + + return new(Invoke); + } + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] + private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithLocalCache_WithDefaultValue() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(IsLocalCacheEnabled = true, DefaultValue = 42)] + public partial int Number { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(42)); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get => field; + set + { + OnNumberSet(ref value); + + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + return; + } + + int __oldValue = field; + + OnNumberChanging(value); + OnNumberChanging(__oldValue, value); + + field = value; + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + OnNumberChanged(__oldValue, value); + } = 42; + } + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int newValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanging(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int))); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_UnsetValue() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValue = GeneratedDependencyProperty.UnsetValue)] + public partial int Number { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(global::Windows.UI.Xaml.DependencyProperty.UnsetValue)); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + + partial void OnNumberPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file static class PropertyChangedCallbacks + { + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback Number() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + OnNumberPropertyChanged(__this, e); + } + + return new(Invoke); + } + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] + private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithDefaultValue() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValue = 42)] + public partial int Number { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(42)); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithDefaultValue_WithCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValue = 42)] + public partial int Number { get; set; } + + partial void OnNumberPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(42, global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file static class PropertyChangedCallbacks + { + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback Number() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + OnNumberPropertyChanged(__this, e); + } + + return new(Invoke); + } + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] + private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithSharedCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + + partial void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file static class PropertyChangedCallbacks + { + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback Number() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + OnPropertyChanged(__this, e); + } + + return new(Invoke); + } + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + private static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithBothCallbacks() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + + partial void OnNumberPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + + partial void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file static class PropertyChangedCallbacks + { + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback Number() + { + static void Invoke(object d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + OnNumberPropertyChanged(__this, e); + OnPropertyChanged(__this, e); + } + + return new(Invoke); + } + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] + private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + private static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_String_WithLocalCache() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(IsLocalCacheEnabled = true)] + public partial string? Name { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? Name + { + get => field; + set + { + OnNameSet(ref value); + + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + return; + } + + string? __oldValue = field; + + OnNameChanging(value); + OnNameChanging(__oldValue, value); + + field = value; + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + OnNameChanged(__oldValue, value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string? propertyValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanging(string? newValue); + + /// 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.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanging(string? oldValue, string? newValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string? oldValue, string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_String_WithNoCaching() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? Name { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_String_WithNoCaching_IsRequired() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public required partial string Name { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public required partial string Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string __unboxedValue = (string)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void MultipleProperties_WithNoCaching_CorrectSpacing() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? FirstName { get; set; } + + [GeneratedDependencyProperty] + public partial string? LastName { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty FirstNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "FirstName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty LastNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "LastName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? FirstName + { + get + { + object? __boxedValue = GetValue(FirstNameProperty); + + OnFirstNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnFirstNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnFirstNameSet(ref value); + + object? __boxedValue = value; + + OnFirstNameSet(ref __boxedValue); + + SetValue(FirstNameProperty, __boxedValue); + + OnFirstNameChanged(value); + } + } + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? LastName + { + get + { + object? __boxedValue = GetValue(LastNameProperty); + + OnLastNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnLastNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnLastNameSet(ref value); + + object? __boxedValue = value; + + OnLastNameSet(ref __boxedValue); + + SetValue(LastNameProperty, __boxedValue); + + OnLastNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } +} diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj new file mode 100644 index 000000000..25312f926 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj @@ -0,0 +1,10 @@ + + + net8.0-windows10.0.17763.0 + enable + preview + true + false + $(DefineConstants);WINDOWS_UWP + + diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets new file mode 100644 index 000000000..3a93502bc --- /dev/null +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets @@ -0,0 +1,70 @@ + + + + + $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML + + + + + + + + + false + true + + + + + + + + + + + + + + + + + + @(CommunityToolkitGeneratedDependencyPropertyCurrentCompilerAssemblyIdentity->'%(Version)') + + + true + + + + + + + diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs new file mode 100644 index 000000000..2516abc63 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs @@ -0,0 +1,24 @@ +// 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. + +#if WINDOWS_UWP +using DependencyProperty = Windows.UI.Xaml.DependencyProperty; +#else +using DependencyProperty = Microsoft.UI.Xaml.DependencyProperty; +#endif + +namespace CommunityToolkit.WinUI; + +/// +/// Provides constant values that can be used as default values for . +/// +public sealed class GeneratedDependencyProperty +{ + /// + /// + /// This constant is only meant to be used in assignments to (because + /// cannot be used in that context, as it is not a constant, but rather a static field). Using this constant in other scenarios is undefined behavior. + /// + public const object UnsetValue = null!; +} diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs new file mode 100644 index 000000000..388fd15b3 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs @@ -0,0 +1,63 @@ +// 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; +#if WINDOWS_UWP +using DependencyObject = Windows.UI.Xaml.DependencyObject; +using DependencyProperty = Windows.UI.Xaml.DependencyProperty; +using PropertyMetadata = Windows.UI.Xaml.PropertyMetadata; +#else +using DependencyObject = Microsoft.UI.Xaml.DependencyObject; +using DependencyProperty = Microsoft.UI.Xaml.DependencyProperty; +using PropertyMetadata = Microsoft.UI.Xaml.PropertyMetadata; +#endif + +namespace CommunityToolkit.WinUI; + +/// +/// An attribute that indicates that a given partial property should generate a backing . +/// In order to use this attribute, the containing type has to inherit from . +/// +/// This attribute can be used as follows: +/// +/// partial class MyClass : DependencyObject +/// { +/// [GeneratedDependencyProperty] +/// public partial string? Name { get; set; } +/// } +/// +/// +/// +/// +/// +/// In order to use this attribute on partial properties, the .NET 9 SDK is required, and C# 13 (or 'preview') must be used. +/// +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] +public sealed class GeneratedDependencyPropertyAttribute : Attribute +{ + /// + /// Gets a value indicating the default value to set for the property. + /// + /// + /// + /// If not set, the default value will be , for all property types. If there is no callback + /// registered for the generated property, will not be set at all. + /// + /// + /// To set the default value to , use . + /// + /// + public object? DefaultValue { get; init; } = null; + + /// + /// Gets a value indicating whether or not property values should be cached locally, to improve performance. + /// This allows completely skipping boxing (for value types) and all WinRT marshalling when setting properties. + /// + /// + /// Local caching is disabled by default. It should be disabled in scenarios where the values of the dependency + /// properties might also be set outside of the partial property implementation, meaning caching would be invalid. + /// + public bool IsLocalCacheEnabled { get; init; } = false; +} From 794d0c313d7979348d9e45968bd8aba6e2606ff7 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 17:23:32 -0600 Subject: [PATCH 003/126] Cleanup and merge ported csproj, rename files --- .../samples/Dependencies.props | 4 -- ...Toolkit.DependencyPropertyGenerator.csproj | 54 +++++++++++++++++++ ...olkit.DependencyPropertyGenerator.targets} | 0 ...Toolkit.GeneratedDependencyProperty.csproj | 10 ---- ...ontrols.DependencyPropertyGenerator.csproj | 15 ------ 5 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj rename components/DependencyPropertyGenerator/src/{CommunityToolkit.GeneratedDependencyProperty.targets => CommunityToolkit.DependencyPropertyGenerator.targets} (100%) delete mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj delete mode 100644 components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj diff --git a/components/DependencyPropertyGenerator/samples/Dependencies.props b/components/DependencyPropertyGenerator/samples/Dependencies.props index e622e1df4..0b0c230ab 100644 --- a/components/DependencyPropertyGenerator/samples/Dependencies.props +++ b/components/DependencyPropertyGenerator/samples/Dependencies.props @@ -11,21 +11,17 @@ - - - - diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj new file mode 100644 index 000000000..ee8af5f60 --- /dev/null +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj @@ -0,0 +1,54 @@ + + + + + DependencyPropertyGenerator + This package contains DependencyPropertyGenerator. + + + CommunityToolkit.DependencyPropertyGeneratorRns + false + + + + + + + + + + + + + System.Diagnostics.CodeAnalysis.NotNullAttribute; + System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; + System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute; + System.Diagnostics.CodeAnalysis.MemberNotNullAttribute; + System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; + System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.targets similarity index 100% rename from components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.targets rename to components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.targets diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj deleted file mode 100644 index 25312f926..000000000 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.GeneratedDependencyProperty.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - net8.0-windows10.0.17763.0 - enable - preview - true - false - $(DefineConstants);WINDOWS_UWP - - diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj deleted file mode 100644 index 84f505cc8..000000000 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.Controls.DependencyPropertyGenerator.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - DependencyPropertyGenerator - This package contains DependencyPropertyGenerator. - - - CommunityToolkit.WinUI.Controls.DependencyPropertyGeneratorRns - false - - - - - From bd3f0c55a2d92db6782b4619d107e9dd5e131be5 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 17:30:58 -0600 Subject: [PATCH 004/126] Fix default namespace and package name for DependencyPropertyGenerator component --- ...tyToolkit.WinUI.DependencyPropertyGenerator.csproj} | 10 +++++----- ...yToolkit.WinUI.DependencyPropertyGenerator.targets} | 0 2 files changed, 5 insertions(+), 5 deletions(-) rename components/DependencyPropertyGenerator/src/{CommunityToolkit.DependencyPropertyGenerator.csproj => CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj} (78%) rename components/DependencyPropertyGenerator/src/{CommunityToolkit.DependencyPropertyGenerator.targets => CommunityToolkit.WinUI.DependencyPropertyGenerator.targets} (100%) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj similarity index 78% rename from components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj rename to components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index ee8af5f60..291f538ab 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -6,7 +6,7 @@ This package contains DependencyPropertyGenerator. - CommunityToolkit.DependencyPropertyGeneratorRns + CommunityToolkit.WinUI.DependencyPropertyGeneratorRns false @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets similarity index 100% rename from components/DependencyPropertyGenerator/src/CommunityToolkit.DependencyPropertyGenerator.targets rename to components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets From 28b5c8377ad4d62f68353a4b7666b22fce6225b6 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 17:49:22 -0600 Subject: [PATCH 005/126] Disable globalusings in DependencyPropertyGenerator --- ...Toolkit.WinUI.DependencyPropertyGenerator.csproj | 13 ++----------- tooling | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index 291f538ab..18a5c1315 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -8,6 +8,7 @@ CommunityToolkit.WinUI.DependencyPropertyGeneratorRns false + false @@ -20,12 +21,7 @@ - System.Diagnostics.CodeAnalysis.NotNullAttribute; - System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; - System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute; - System.Diagnostics.CodeAnalysis.MemberNotNullAttribute; - System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute; - System.Runtime.CompilerServices.CallerArgumentExpressionAttribute; + System.Runtime.CompilerServices.IsExternalInit; @@ -46,9 +42,4 @@ - - - - - diff --git a/tooling b/tooling index 2ec08a780..66d28745e 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 2ec08a780daaecadd9705a2ab0e4cdde4c6168e2 +Subproject commit 66d28745edaf5eec07a25135f7eb8da54b43deb5 From 8e7694f65f16e2b3e82af8ad3103c6e81b8ffc61 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 18:04:21 -0600 Subject: [PATCH 006/126] Move files, fix folder name --- .../AnalyzerReleases.Shipped.md | 0 .../AnalyzerReleases.Unshipped.md | 0 ...Toolkit.DependencyPropertyGenerator.SourceGenerators.csproj} | 0 .../Constants/WellKnownTrackingNames.cs | 0 .../Constants/WellKnownTypeNames.cs | 0 .../DependencyPropertyGenerator.Execute.cs | 0 .../DependencyPropertyGenerator.cs | 0 .../Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs | 0 .../InvalidPropertyContainingTypeDeclarationAnalyzer.cs | 0 .../Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs | 2 +- .../Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs | 0 .../Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs | 0 .../Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs | 0 .../Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs | 0 .../Diagnostics/DiagnosticDescriptors.cs | 0 .../Extensions/AccessibilityExtensions.cs | 0 .../Extensions/AttributeDataExtensions.cs | 0 .../Extensions/CompilationExtensions.cs | 0 .../Extensions/ISymbolExtensions.cs | 0 .../Extensions/ITypeSymbolExtensions.cs | 0 .../Extensions/IncrementalValueProviderExtensions.cs | 0 .../Extensions/IndentedTextWriterExtensions.cs | 0 .../Extensions/SyntaxNodeExtensions.cs | 0 .../Helpers/EquatableArray{T}.cs | 0 .../Helpers/HashCode.cs | 0 .../Helpers/ImmutableArrayBuilder{T}.cs | 0 .../Helpers/IndentedTextWriter.cs | 0 .../Helpers/ObjectPool{T}.cs | 0 .../Models/DependencyPropertyInfo.cs | 0 .../Models/HierarchyInfo.cs | 0 .../Models/TypeInfo.cs | 0 .../Models/TypedConstantInfo.Factory.cs | 0 .../Models/TypedConstantInfo.cs | 0 .../CommunityToolkit.DependencyPropertyGenerator.Tests.csproj} | 0 .../Helpers/CSharpAnalyzerTest{TAnalyzer}.cs | 0 .../Helpers/CSharpGeneratorTest{TGenerator}.cs | 0 .../Test_Analyzers.cs | 0 .../Test_DependencyPropertyGenerator.PostInitialization.cs | 0 .../Test_DependencyPropertyGenerator.cs | 0 39 files changed, 1 insertion(+), 1 deletion(-) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/AnalyzerReleases.Shipped.md (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/AnalyzerReleases.Unshipped.md (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj} (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Constants/WellKnownTrackingNames.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Constants/WellKnownTypeNames.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/DependencyPropertyGenerator.Execute.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/DependencyPropertyGenerator.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs (98%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Diagnostics/DiagnosticDescriptors.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/AccessibilityExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/AttributeDataExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/CompilationExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/ISymbolExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/ITypeSymbolExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/IncrementalValueProviderExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/IndentedTextWriterExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Extensions/SyntaxNodeExtensions.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Helpers/EquatableArray{T}.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Helpers/HashCode.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Helpers/ImmutableArrayBuilder{T}.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Helpers/IndentedTextWriter.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Helpers/ObjectPool{T}.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Models/DependencyPropertyInfo.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Models/HierarchyInfo.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Models/TypeInfo.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Models/TypedConstantInfo.Factory.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/Models/TypedConstantInfo.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj => CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj} (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests => CommunityToolkit.DependencyPropertyGenerator.Tests}/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests => CommunityToolkit.DependencyPropertyGenerator.Tests}/Helpers/CSharpGeneratorTest{TGenerator}.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests => CommunityToolkit.DependencyPropertyGenerator.Tests}/Test_Analyzers.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests => CommunityToolkit.DependencyPropertyGenerator.Tests}/Test_DependencyPropertyGenerator.PostInitialization.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.Tests => CommunityToolkit.DependencyPropertyGenerator.Tests}/Test_DependencyPropertyGenerator.cs (100%) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Shipped.md rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/AnalyzerReleases.Unshipped.md rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator.csproj rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTrackingNames.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Constants/WellKnownTypeNames.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.Execute.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/DependencyPropertyGenerator.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs similarity index 98% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs index 457c6a353..92e801dd1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs @@ -80,7 +80,7 @@ public override void Initialize(AnalysisContext context) { SyntaxNode propertyNode = propertyReference.GetSyntax(context.CancellationToken); - if (!IsValidPropertyDeclaration(propertyNode)) + // if (!IsValidPropertyDeclaration(propertyNode)) { context.ReportDiagnostic(Diagnostic.Create( InvalidPropertyDeclaration, diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AccessibilityExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AttributeDataExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/AttributeDataExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AttributeDataExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/CompilationExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ISymbolExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ISymbolExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ISymbolExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/ITypeSymbolExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IncrementalValueProviderExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/IndentedTextWriterExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxNodeExtensions.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Extensions/SyntaxNodeExtensions.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxNodeExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/EquatableArray{T}.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/EquatableArray{T}.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/EquatableArray{T}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/HashCode.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ImmutableArrayBuilder{T}.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/IndentedTextWriter.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Helpers/ObjectPool{T}.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/DependencyPropertyInfo.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/HierarchyInfo.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypeInfo.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.Factory.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/Models/TypedConstantInfo.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/CommunityToolkit.GeneratedDependencyProperty.Tests.csproj rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_Analyzers.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.Tests/Test_DependencyPropertyGenerator.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs From 54f0445b7889aa8b7d4496ea7391f75385ea2ddf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 16:08:31 -0800 Subject: [PATCH 007/126] Bring back embedded resources --- ...yPropertyGenerator.SourceGenerators.csproj | 6 +- ...t.DependencyPropertyGenerator.Tests.csproj | 8 +- .../GeneratedDependencyProperty.cs | 36 +++++++++ .../GeneratedDependencyPropertyAttribute.cs | 75 +++++++++++++++++++ 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index b64446802..21f4f3395 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -23,8 +23,8 @@ - - - + + + diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index ca2d08512..34b79836f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -30,12 +30,12 @@ diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs new file mode 100644 index 000000000..5364e9c10 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs @@ -0,0 +1,36 @@ +// +#pragma warning disable +#nullable enable + +// 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. + +#if GENERATED_DEPENDENCY_PROPERTY_PRIVATE_ASSETS_ALL_MODE + +namespace CommunityToolkit.WinUI +{ +#if GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML + using DependencyProperty = global::Windows.UI.Xaml.DependencyProperty; +#else + using DependencyProperty = global::Microsoft.UI.Xaml.DependencyProperty; +#endif + + /// + /// Provides constant values that can be used as default values for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("", "")] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class GeneratedDependencyProperty + { + /// + /// + /// This constant is only meant to be used in assignments to (because + /// cannot be used in that context, as it is not a constant, but rather a static field). Using this constant in other scenarios is undefined behavior. + /// + public const object UnsetValue = null!; + } +} + +#endif diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs new file mode 100644 index 000000000..752240f7e --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs @@ -0,0 +1,75 @@ +// +#pragma warning disable +#nullable enable + +// 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. + +#if GENERATED_DEPENDENCY_PROPERTY_PRIVATE_ASSETS_ALL_MODE + +namespace CommunityToolkit.WinUI +{ +#if GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML + using DependencyObject = global::Windows.UI.Xaml.DependencyObject; + using DependencyProperty = global::Windows.UI.Xaml.DependencyProperty; + using PropertyMetadata = global::Windows.UI.Xaml.PropertyMetadata; +#else + using DependencyObject = global::Microsoft.UI.Xaml.DependencyObject; + using DependencyProperty = global::Microsoft.UI.Xaml.DependencyProperty; + using PropertyMetadata = global::Microsoft.UI.Xaml.PropertyMetadata; +#endif + + /// + /// An attribute that indicates that a given partial property should generate a backing . + /// In order to use this attribute, the containing type has to inherit from . + /// + /// This attribute can be used as follows: + /// + /// partial class MyClass : DependencyObject + /// { + /// [GeneratedDependencyProperty] + /// public partial string? Name { get; set; } + /// } + /// + /// + /// + /// + /// + /// In order to use this attribute on partial properties, the .NET 9 SDK is required, and C# 13 (or 'preview') must be used. + /// + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + [global::System.CodeDom.Compiler.GeneratedCode("", "")] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.Diagnostics.Conditional("GENERATED_DEPENDENCY_PROPERTY_PRIVATE_ASSETS_ALL_PRESERVE_ATTRIBUTES")] + internal sealed class GeneratedDependencyPropertyAttribute : global::System.Attribute + { + /// + /// Gets a value indicating the default value to set for the property. + /// + /// + /// + /// If not set, the default value will be , for all property types. If there is no callback + /// registered for the generated property, will not be set at all. + /// + /// + /// To set the default value to , use . + /// + /// + public object? DefaultValue { get; init; } = null; + + /// + /// Gets a value indicating whether or not property values should be cached locally, to improve performance. + /// This allows completely skipping boxing (for value types) and all WinRT marshalling when setting properties. + /// + /// + /// Local caching is disabled by default. It should be disabled in scenarios where the values of the dependency + /// properties might also be set outside of the partial property implementation, meaning caching would be invalid. + /// + public bool IsLocalCacheEnabled { get; init; } = false; + } +} + +#endif From 9aec6010c224a9682b8205d4eee9dd05b3311b1c Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 18:27:04 -0600 Subject: [PATCH 008/126] Fix folder name typo --- .../EmbeddedResources/GeneratedDependencyProperty.cs | 0 .../EmbeddedResources/GeneratedDependencyPropertyAttribute.cs | 0 tooling | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/EmbeddedResources/GeneratedDependencyProperty.cs (100%) rename components/DependencyPropertyGenerator/{CommunityToolkit.GeneratedDependencyProperty.SourceGenerator => CommunityToolkit.DependencyPropertyGenerator.SourceGenerators}/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs (100%) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyProperty.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs similarity index 100% rename from components/DependencyPropertyGenerator/CommunityToolkit.GeneratedDependencyProperty.SourceGenerator/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs rename to components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs diff --git a/tooling b/tooling index 66d28745e..c5473d4ab 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 66d28745edaf5eec07a25135f7eb8da54b43deb5 +Subproject commit c5473d4abd1eb162ba9d6512439438a61e0b6252 From f472ab0117926e1a99d8c2c4884ac393adb6ef0d Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 2 Dec 2024 20:04:17 -0600 Subject: [PATCH 009/126] Fix project references and update embedded resource paths in DependencyPropertyGenerator tests --- ...ommunityToolkit.DependencyPropertyGenerator.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index 34b79836f..e36f52807 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -23,18 +23,18 @@ - + From a559c58e75e5ea27caae8470687496073c21185d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 18:11:32 -0800 Subject: [PATCH 010/126] Fix wrong paths in test project --- ...munityToolkit.DependencyPropertyGenerator.Tests.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index e36f52807..d30603364 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -23,18 +23,18 @@ - - + + From bf62d4d84a1d100a367f30c9af082b650c71de3e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 18:14:39 -0800 Subject: [PATCH 011/126] Fix more wrong paths --- ...ommunityToolkit.DependencyPropertyGenerator.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index d30603364..7828f4b89 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -23,18 +23,18 @@ - + From eb854c9bc7d45da5feaf3fa30c8e9d54ad6ed8ae Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 20:49:16 -0800 Subject: [PATCH 012/126] Fix some warnings in the test project --- .../Test_DependencyPropertyGenerator.PostInitialization.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs index 66777d619..1baa9750d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.PostInitialization.cs @@ -33,7 +33,7 @@ public partial class MyControl : DependencyObject string fileName = $"{typeName}.g.cs"; string sourceText; - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fileName)) + using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fileName)!) using (StreamReader reader = new(stream)) { sourceText = reader.ReadToEnd(); @@ -41,7 +41,7 @@ public partial class MyControl : DependencyObject string updatedSourceText = sourceText .Replace("", "CommunityToolkit.WinUI.DependencyPropertyGenerator") - .Replace("", typeof(DependencyPropertyGenerator).Assembly.GetName().Version.ToString()); + .Replace("", typeof(DependencyPropertyGenerator).Assembly.GetName().Version!.ToString()); CSharpGeneratorTest.VerifySources(source, (fileName, updatedSourceText), languageVersion: LanguageVersion.CSharp13); } From df9307921cfa565d02e386858577e6ce15f27469 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 20:53:50 -0800 Subject: [PATCH 013/126] Create .gitattributes --- components/DependencyPropertyGenerator/.gitattributes | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 components/DependencyPropertyGenerator/.gitattributes diff --git a/components/DependencyPropertyGenerator/.gitattributes b/components/DependencyPropertyGenerator/.gitattributes new file mode 100644 index 000000000..64d6ecc10 --- /dev/null +++ b/components/DependencyPropertyGenerator/.gitattributes @@ -0,0 +1,10 @@ +# All file types: +# - Treat as text +# - Normalize to LF line endings +* text=auto eol=lf + +# Explicit settings for well known types +*.cs text eol=lf +*.csproj text eol=lf +*.projitems text eol=lf +*.shprroj text eol=lf \ No newline at end of file From 03f041ee7e12a42e664cb3a9c83c807ced808b77 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 20:56:42 -0800 Subject: [PATCH 014/126] Fix newlines to LF --- .../AnalyzerReleases.Unshipped.md | 2 +- .../Extensions/AccessibilityExtensions.cs | 2 +- .../Extensions/IndentedTextWriterExtensions.cs | 2 +- .../Helpers/HashCode.cs | 2 +- .../Helpers/IndentedTextWriter.cs | 2 +- .../Helpers/ObjectPool{T}.cs | 2 +- .../Models/HierarchyInfo.cs | 2 +- .../Models/TypeInfo.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md index 6a6dd08d1..17d4678ce 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -1,2 +1,2 @@ -; Unshipped analyzer release +; Unshipped analyzer release ; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs index 413ecc2c7..f7154e446 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs @@ -29,4 +29,4 @@ public static string GetExpression(this Accessibility accessibility) _ => "" }; } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs index 688db2077..01a9a5ec2 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IndentedTextWriterExtensions.cs @@ -101,4 +101,4 @@ public static void WriteInitializationExpressions( } } } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs index 8d9135cc7..9e3728b56 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/HashCode.cs @@ -500,4 +500,4 @@ private static uint RotateLeft(uint value, int offset) { return (value << offset) | (value >> (32 - offset)); } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs index 9bfc74b65..6d88ecb97 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs @@ -512,4 +512,4 @@ public void AppendFormatted(T value, string? format) this.handler.AppendFormatted(value, format); } } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs index 723d0a73f..44b103abc 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/ObjectPool{T}.cs @@ -151,4 +151,4 @@ private struct Element /// internal T? Value; } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs index 67393f1e9..5bb928710 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/HierarchyInfo.cs @@ -137,4 +137,4 @@ public string GetFullyQualifiedTypeName() return fullyQualifiedTypeName.ToString(); } -} \ No newline at end of file +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs index a1b5e7e02..daa0b27cd 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypeInfo.cs @@ -29,4 +29,4 @@ public string GetTypeKeyword() _ => "class" }; } -} \ No newline at end of file +} From d503756f5f90b17298706b01be0e795324002dc6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 20:58:29 -0800 Subject: [PATCH 015/126] Use 'WellKnownTypeNames' in all analyzers --- .../Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs | 2 +- .../InvalidPropertyContainingTypeDeclarationAnalyzer.cs | 2 +- .../Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs | 3 ++- .../Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs | 3 ++- .../Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs | 3 ++- .../Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs | 3 ++- .../Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs | 3 ++- 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs index 1206c2fef..0c204d0c5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs @@ -29,7 +29,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Get the 'DependencyPropertyChangedEventArgs' symbol if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs) is not { } dependencyPropertyChangedEventArgsSymbol) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs index b99a9f5f0..af90b2470 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs @@ -29,7 +29,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Get the 'DependencyObject' symbol if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject) is not { } dependencyObjectSymbol) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs index 92e801dd1..062d84b60 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Threading; +using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -36,7 +37,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); context.RegisterSymbolAction(context => { diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs index a7d99b6ff..649ed5373 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -28,7 +29,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Attempt to also get the '[MaybeNull]' symbols (there might be multiples, due to polyfills) ImmutableArray maybeNullAttributeSymbol = context.Compilation.GetTypesByMetadataName("System.Diagnostics.CodeAnalysis.MaybeNullAttribute"); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs index c82c02df0..60a26e2b6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -35,7 +36,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Get the '[GeneratedCode]' symbol if (context.Compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute") is not { } generatedCodeAttributeSymbol) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs index 8b5d6b157..29df7b907 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -30,7 +31,7 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); context.RegisterSymbolAction(context => { diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs index 200769544..c05b15809 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UnsupportedCSharpLanguageVersionAnalyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -39,7 +40,7 @@ public override void Initialize(AnalysisContext context) } // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName("CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"); + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); context.RegisterSymbolAction(context => { From 8846a706441920a44a035c219d94ec41cc504ac0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 23:15:09 -0800 Subject: [PATCH 016/126] Remove global usings --- ...olkit.DependencyPropertyGenerator.SourceGenerators.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 21f4f3395..17f887803 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -27,4 +27,9 @@ + + + + + From 51d44bd95bee9e5c180b5990f22f474f23666c12 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 23:15:43 -0800 Subject: [PATCH 017/126] Finish 'InvalidPropertyDefaultValueTypeAnalyzer' --- .../Constants/WellKnownTypeNames.cs | 5 + .../DependencyPropertyGenerator.Execute.cs | 25 +++- ...InvalidPropertyDefaultValueTypeAnalyzer.cs | 128 +++++++++++++++++ ...nvalidPropertyDefaultValueTypeAttribute.cs | 129 ------------------ 4 files changed, 155 insertions(+), 132 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAnalyzer.cs delete mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs index cc2808537..60194c1db 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs @@ -14,6 +14,11 @@ internal static class WellKnownTypeNames /// public const string GeneratedDependencyPropertyAttribute = "CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"; + /// + /// The fully qualified type name for the GeneratedDependencyProperty type. + /// + public const string GeneratedDependencyProperty = "CommunityToolkit.WinUI.GeneratedDependencyProperty"; + /// /// The fully qualified type name for the XAML namespace. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index aa5164dba..f14961453 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; namespace CommunityToolkit.GeneratedDependencyProperty; @@ -227,10 +228,28 @@ public static TypedConstantInfo GetDefaultValue( return TypedConstantInfo.Create(defaultValue); } - // Handle 'UnsetValue' as well - if (InvalidPropertyDefaultValueTypeAttribute.IsDependencyPropertyUnsetValue(attributeData, semanticModel, token)) + // If we do have a default value, we also want to check whether it's the special 'UnsetValue' placeholder. + // To do so, we get the application syntax, find the argument, then get the operation and inspect it. + if (attributeData.ApplicationSyntaxReference?.GetSyntax(token) is AttributeSyntax attributeSyntax) { - return UnsetValueInfo; + foreach (AttributeArgumentSyntax attributeArgumentSyntax in attributeSyntax.ArgumentList?.Arguments ?? []) + { + // Let's see whether the current argument is the one that set the 'DefaultValue' property + if (attributeArgumentSyntax.NameEquals?.Name.Identifier.Text is "DefaultValue") + { + IOperation? operation = semanticModel.GetOperation(attributeArgumentSyntax.Expression, token); + + // Double check that it's a constant field reference (it could also be a literal of some kind, etc.) + if (operation is IFieldReferenceOperation { Field: { Name: "UnsetValue" } fieldSymbol }) + { + // Last step: we want to validate that the reference is actually to the special placeholder + if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName(WellKnownTypeNames.GeneratedDependencyProperty)) + { + return UnsetValueInfo; + } + } + } + } } // Otherwise, the value has been explicitly set to 'null', so let's respect that diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAnalyzer.cs new file mode 100644 index 000000000..485819482 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAnalyzer.cs @@ -0,0 +1,128 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used with an invalid default value type. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyDefaultValueTypeAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + InvalidPropertyDefaultValueNull, + InvalidPropertyDefaultValueType + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + context.RegisterOperationAction(context => + { + // We only care about attributes on properties + if (context.ContainingSymbol is not IPropertySymbol propertySymbol) + { + return; + } + + // Make sure the attribute operation is valid, and that we can get the attribute type symbol + if (context.Operation is not IAttributeOperation { Operation: IObjectCreationOperation { Type: INamedTypeSymbol attributeTypeSymbol } objectOperation }) + { + return; + } + + // Filter out all attributes of other types + if (!generatedDependencyPropertyAttributeSymbols.Contains(attributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } + + // Also get the actual attribute data for '[GeneratedDependencyProperty]' (this should always succeed at this point) + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // Get the default value, if present (if it's not set, nothing to do) + if (!attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue)) + { + return; + } + + bool isNullableValueType = propertySymbol.Type is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + bool isNullableType = !propertySymbol.Type.IsValueType || isNullableValueType; + + // If the value is 'null', handle all possible cases: + // - Special placeholder for 'UnsetValue' + // - Explicit 'null' value + if (defaultValue.IsNull) + { + // Go through all named arguments of the attribute to look for 'UnsetValue' + foreach (IOperation argumentOperation in objectOperation.Initializer?.Initializers ?? []) + { + // We found its assignment: check if it's the 'UnsetValue' placeholder + if (argumentOperation is ISimpleAssignmentOperation { Value: IFieldReferenceOperation { Field: { Name: "UnsetValue" } fieldSymbol } }) + { + // Validate that the reference is actually to the special placeholder + if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName(WellKnownTypeNames.GeneratedDependencyProperty)) + { + return; + } + + // If it's not a match, we can just stop iterating: we know for sure the value is something else explicitly set + break; + } + } + + // Warn if the value is not nullable + if (!isNullableType) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDefaultValueNull, + attributeData.GetLocation(), + propertySymbol, + propertySymbol.Type)); + } + } + else + { + // Get the target type with a special case for 'Nullable' + ITypeSymbol propertyTypeSymbol = isNullableValueType + ? ((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0] + : propertySymbol.Type; + + // Warn if the type of the default value is not compatible + if (!SymbolEqualityComparer.Default.Equals(propertyTypeSymbol, defaultValue.Type)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDefaultValueType, + attributeData.GetLocation(), + propertySymbol, + propertySymbol.Type, + defaultValue.Value, + defaultValue.Type)); + } + } + }, OperationKind.Attribute); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs deleted file mode 100644 index 062d84b60..000000000 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueTypeAttribute.cs +++ /dev/null @@ -1,129 +0,0 @@ -// 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.Collections.Immutable; -using System.Threading; -using CommunityToolkit.GeneratedDependencyProperty.Constants; -using CommunityToolkit.GeneratedDependencyProperty.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Operations; -using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; - -namespace CommunityToolkit.GeneratedDependencyProperty; - -/// -/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used with an invalid default value type. -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class InvalidPropertyDefaultValueTypeAttribute : DiagnosticAnalyzer -{ - /// - public override ImmutableArray SupportedDiagnostics { get; } = - [ - InvalidPropertyDefaultValueNull, - InvalidPropertyDefaultValueType - ]; - - /// - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - - context.RegisterCompilationStartAction(static context => - { - // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) - ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); - - context.RegisterSymbolAction(context => - { - // We're intentionally only looking for properties here - if (context.Symbol is not IPropertySymbol propertySymbol) - { - return; - } - - // If the property isn't using '[GeneratedDependencyProperty]', there's nothing to do - if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) - { - return; - } - - // Get the default value, if present (if it's not set, nothing to do) - if (!attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue)) - { - return; - } - - // Skip 'UnsetValue', that's special - - bool isNullableValueType = propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; - bool isNullableType = !propertySymbol.Type.IsValueType || isNullableValueType; - - // Check for invalid 'null' default values - if (defaultValue.IsNull && !isNullableType) - { - context.ReportDiagnostic(Diagnostic.Create( - InvalidPropertyDefaultValueNull, - attributeData.GetLocation(), - propertySymbol, - propertySymbol.Type)); - - return; - } - - - foreach (SyntaxReference propertyReference in propertySymbol.DeclaringSyntaxReferences) - { - SyntaxNode propertyNode = propertyReference.GetSyntax(context.CancellationToken); - - // if (!IsValidPropertyDeclaration(propertyNode)) - { - context.ReportDiagnostic(Diagnostic.Create( - InvalidPropertyDeclaration, - attributeData.GetLocation(), - propertySymbol)); - - return; - } - } - }, SymbolKind.Property); - }); - } - - internal static bool IsDependencyPropertyUnsetValue( - AttributeData attributeData, - SemanticModel semanticModel, - CancellationToken token) - { - // If we do have a default value, we also want to check whether it's the special 'UnsetValue' placeholder. - // To do so, we get the application syntax, find the argument, then get the operation and inspect it. - if (attributeData.ApplicationSyntaxReference?.GetSyntax(token) is AttributeSyntax attributeSyntax) - { - foreach (AttributeArgumentSyntax attributeArgumentSyntax in attributeSyntax.ArgumentList?.Arguments ?? []) - { - // Let's see whether the current argument is the one that set the 'DefaultValue' property - if (attributeArgumentSyntax.NameEquals?.Name.Identifier.Text is "DefaultValue") - { - IOperation? operation = semanticModel.GetOperation(attributeArgumentSyntax.Expression, token); - - // Double check that it's a constant field reference (it could also be a literal of some kind, etc.) - if (operation is IFieldReferenceOperation { Field: { Name: "UnsetValue" } fieldSymbol }) - { - // Last step: we want to validate that the reference is actually to the special placeholder - if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName("CommunityToolkit.WinUI.GeneratedDependencyProperty")) - { - return true; - } - } - } - } - } - - return false; - } -} From 6b2d0cba6bf23b2d4218bd3150c5be82bcb7a250 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 2 Dec 2024 23:15:49 -0800 Subject: [PATCH 018/126] Add unit tests --- .../Test_Analyzers.cs | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 35164ff21..deaa19b7b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -743,4 +743,161 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_NoDefaultValue_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string?")] + [DataRow("int")] + [DataRow("int?")] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_UnsetValue_DoesNotWarn(string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = GeneratedDependencyProperty.UnsetValue)] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string")] + [DataRow("int?")] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_NullValue_Nullable_DoesNotWarn(string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = null)] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string", "\"test\"")] + [DataRow("int", "42")] + [DataRow("double", "3.14")] + [DataRow("int?", "42")] + [DataRow("double?", "3.14")] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_CompatibleType_DoesNotWarn(string propertyType, string defaultValueType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = {{defaultValueType}})] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_NullValue_NonNullable_Warns() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0010:GeneratedDependencyProperty(DefaultValue = null)|}] + public partial int {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string", "42")] + [DataRow("string", "3.14")] + [DataRow("int", "\"test\"")] + [DataRow("int?", "\"test\"")] + public async Task InvalidPropertyDefaultValueTypeAnalyzer_IncompatibleType_Warns(string propertyType, string defaultValueType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0011:GeneratedDependencyProperty(DefaultValue = {{defaultValueType}})|}] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From 6c1ceb736f058817c9d9f22f6abe2e03503c9b44 Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 11:52:24 -0600 Subject: [PATCH 019/126] Consolidate GlobalUsings imports around tooling --- Directory.Build.targets | 1 - Windows.Toolkit.Common.props | 4 ---- .../AppServices.SourceGenerators.Tests.csproj | 4 ---- .../CommunityToolkit.AppServices.SourceGenerators.csproj | 5 ----- .../AppServices/src/CommunityToolkit.AppServices.csproj | 6 +----- ...lkit.DependencyPropertyGenerator.SourceGenerators.csproj | 5 ----- .../samples/DependencyPropertyGeneratorCustomSample.xaml.cs | 4 ++-- ...t.Extensions.DependencyInjection.SourceGenerators.csproj | 5 ----- .../CommunityToolkit.Extensions.DependencyInjection.csproj | 5 ----- tooling | 2 +- 10 files changed, 4 insertions(+), 37 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 22dfa3191..10a703183 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -22,7 +22,6 @@ - \ No newline at end of file diff --git a/Windows.Toolkit.Common.props b/Windows.Toolkit.Common.props index 83f9ae093..773d2effe 100644 --- a/Windows.Toolkit.Common.props +++ b/Windows.Toolkit.Common.props @@ -29,8 +29,4 @@ true true - - - - diff --git a/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj b/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj index 46b9111b5..3fceba82d 100644 --- a/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj +++ b/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj @@ -16,8 +16,4 @@ - - - - diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj index 64d80c88a..78f3c3ccc 100644 --- a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj @@ -22,9 +22,4 @@ - - - - - diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index dcab9109a..42a5c0199 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -7,6 +7,7 @@ CommunityToolkit.AppServices $(PackageIdPrefix).$(ToolkitComponentName) false + false @@ -57,9 +58,4 @@ - - - - - diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 17f887803..21f4f3395 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -27,9 +27,4 @@ - - - - - diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs index 1b07afdd3..78ac1454e 100644 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using CommunityToolkit.WinUI.Controls; +using CommunityToolkit.WinUI; namespace DependencyPropertyGeneratorExperiment.Samples; @@ -12,7 +12,7 @@ namespace DependencyPropertyGeneratorExperiment.Samples; [ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] [ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] -[ToolkitSample(id: nameof(DependencyPropertyGeneratorCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(DependencyPropertyGenerator)} custom control.")] +[ToolkitSample(id: nameof(DependencyPropertyGeneratorCustomSample), "Custom control", description: $"A sample for showing how to create and use the DependencyPropertyGenerator.")] public sealed partial class DependencyPropertyGeneratorCustomSample : Page { public DependencyPropertyGeneratorCustomSample() diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj index e80e6cf87..23e3243a3 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj @@ -22,9 +22,4 @@ - - - - - \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index c339fb59f..4aa44f5af 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -30,9 +30,4 @@ - - - - - diff --git a/tooling b/tooling index c5473d4ab..05fbc3eac 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit c5473d4abd1eb162ba9d6512439438a61e0b6252 +Subproject commit 05fbc3eacff1ae0f1ef4865ff841bcefe9f8899f From d9bec9403f8cea21b9f87a3d67416e7d1968e172 Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 12:15:37 -0600 Subject: [PATCH 020/126] Fixed namespace errors when running under Uno --- .../src/GeneratedDependencyProperty.cs | 2 +- .../src/GeneratedDependencyPropertyAttribute.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs index 2516abc63..e5dbcc3a5 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if WINDOWS_UWP +#if WINDOWS_UWP || HAS_UNO using DependencyProperty = Windows.UI.Xaml.DependencyProperty; #else using DependencyProperty = Microsoft.UI.Xaml.DependencyProperty; diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs index 388fd15b3..d7b016cfe 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -#if WINDOWS_UWP +#if WINDOWS_UWP || HAS_UNO using DependencyObject = Windows.UI.Xaml.DependencyObject; using DependencyProperty = Windows.UI.Xaml.DependencyProperty; using PropertyMetadata = Windows.UI.Xaml.PropertyMetadata; From 291f7fa93f58d69c0f19379894c42dba4259ec48 Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 12:15:47 -0600 Subject: [PATCH 021/126] Update tooling submodule --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 05fbc3eac..7e6206391 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 05fbc3eacff1ae0f1ef4865ff841bcefe9f8899f +Subproject commit 7e620639169a85e9430504563f00426ab39c85d2 From 9637c3a2bdb68f6621fc9e0ab00ecb4f6e2cce88 Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 14:08:19 -0600 Subject: [PATCH 022/126] Remove LangVersion setting from DependencyPropertyGenerator project --- ...tyToolkit.DependencyPropertyGenerator.SourceGenerators.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 21f4f3395..32c4c1233 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -1,7 +1,6 @@ netstandard2.0 - 13.0 enable true true From 9f0809d50b274c8cd0a2059f2420bc11f472d1fc Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 14:14:23 -0600 Subject: [PATCH 023/126] Ran XAML styler --- .../samples/DependencyPropertyGeneratorCustomSample.xaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml index 0d45a88ec..75d2cb3a7 100644 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml @@ -3,6 +3,4 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:CommunityToolkit.WinUI.Controls" - xmlns:local="using:DependencyPropertyGeneratorExperiment.Samples"> - - + xmlns:local="using:DependencyPropertyGeneratorExperiment.Samples" /> From 8436bde6d046b9fa580857f6bb02bf7d3ab17c0c Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 14:17:13 -0600 Subject: [PATCH 024/126] Remove empty samples --- .../samples/DependencyPropertyGenerator.md | 6 ---- ...pendencyPropertyGeneratorCustomSample.xaml | 6 ---- ...dencyPropertyGeneratorCustomSample.xaml.cs | 30 ------------------- ...ependencyInjection.SourceGenerators.csproj | 25 ---------------- 4 files changed, 67 deletions(-) delete mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml delete mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs delete mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md index 35e7939c9..351101d5c 100644 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md +++ b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md @@ -24,9 +24,3 @@ icon: assets/icon.png TODO: Fill in information about this experiment and how to get started here... -## Custom Control - -You can inherit from an existing component as well, like `Panel`, this example shows a control without a -XAML Style that will be more light-weight to consume by an app developer: - -> [!Sample DependencyPropertyGeneratorCustomSample] diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml deleted file mode 100644 index 75d2cb3a7..000000000 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml +++ /dev/null @@ -1,6 +0,0 @@ - - diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs b/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs deleted file mode 100644 index 78ac1454e..000000000 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGeneratorCustomSample.xaml.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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 CommunityToolkit.WinUI; - -namespace DependencyPropertyGeneratorExperiment.Samples; - -/// -/// An example sample page of a custom control inheriting from Panel. -/// -[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] -[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] - -[ToolkitSample(id: nameof(DependencyPropertyGeneratorCustomSample), "Custom control", description: $"A sample for showing how to create and use the DependencyPropertyGenerator.")] -public sealed partial class DependencyPropertyGeneratorCustomSample : Page -{ - public DependencyPropertyGeneratorCustomSample() - { - this.InitializeComponent(); - } - - // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 - public static Orientation ConvertStringToOrientation(string orientation) => orientation switch - { - "Vertical" => Orientation.Vertical, - "Horizontal" => Orientation.Horizontal, - _ => throw new System.NotImplementedException(), - }; -} diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj deleted file mode 100644 index 23e3243a3..000000000 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - netstandard2.0 - false - true - - - $(NoWarn);CS8500 - - - - - - - - - - - - \ No newline at end of file From 1babcec58e3ab263970986e7ade5a180ba2906cb Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 14:27:58 -0600 Subject: [PATCH 025/126] Remove empty component doc to fix CI error --- .../samples/DependencyPropertyGenerator.md | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md diff --git a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md b/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md deleted file mode 100644 index 351101d5c..000000000 --- a/components/DependencyPropertyGenerator/samples/DependencyPropertyGenerator.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: DependencyPropertyGenerator -author: githubaccount -description: TODO: Your experiment's description here -keywords: DependencyPropertyGenerator, Control, Layout -dev_langs: - - csharp -category: Controls -subcategory: Layout -discussion-id: 0 -issue-id: 0 -icon: assets/icon.png ---- - - - - - - - - - -# DependencyPropertyGenerator - -TODO: Fill in information about this experiment and how to get started here... - From c2351acfcc5c4775b5817b4a0a5ac31d906793f8 Mon Sep 17 00:00:00 2001 From: Arlo Date: Tue, 3 Dec 2024 14:47:29 -0600 Subject: [PATCH 026/126] Remove unused test files from DependencyPropertyGenerator project --- .../DependencyPropertyGenerator.Tests.projitems | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems index 3156f4a9f..f30608823 100644 --- a/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems +++ b/components/DependencyPropertyGenerator/tests/DependencyPropertyGenerator.Tests.projitems @@ -8,16 +8,4 @@ DependencyPropertyGeneratorExperiment.Tests - - - - ExampleDependencyPropertyGeneratorTestPage.xaml - - - - - Designer - MSBuild:Compile - - \ No newline at end of file From 471c16116b7a587ad2c420a0fb3a1340cad88302 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 18:04:36 -0600 Subject: [PATCH 027/126] Update .NET version to 9.0 in Dockerfile, devcontainer.json, build.yml, and global.json --- .devcontainer/Dockerfile | 7 ++----- .devcontainer/devcontainer.json | 2 +- .github/workflows/build.yml | 4 +--- global.json | 2 +- tooling | 2 +- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f09593147..6935449fd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,5 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.208.0/containers/dotnet/.devcontainer/base.Dockerfile - -# [Choice] .NET version: 6.0, 5.0, 3.1, 6.0-bullseye, 5.0-bullseye, 3.1-bullseye, 6.0-focal, 5.0-focal, 3.1-focal, etc -ARG VARIANT="8.0-bullseye-slim" -FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} +# See https://github.com/devcontainers/images/tree/main/src/dotnet for image choices +FROM mcr.microsoft.com/vscode/devcontainers/dotnet:9.0 # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="none" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d21671858..f616c78fc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "args": { // Update 'VARIANT' to pick a .NET Core version: 3.1, 5.0, 6.0 // Append -bullseye or -focal to pin to an OS version. - "VARIANT": "6.0", + "VARIANT": "9.0", // Options "NODE_VERSION": "lts/*" } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc9dec043..1cc92604e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,7 @@ on: merge_group: env: - DOTNET_VERSION: ${{ '8.0.201' }} - DOTNET_INSTALL_DIR: dotnet-install - DOTNET_ROOT: dotnet-install + DOTNET_VERSION: ${{ '9.0.x' }} ENABLE_DIAGNOSTICS: false #COREHOST_TRACE: 1 MSBUILD_VERBOSITY: normal diff --git a/global.json b/global.json index 91187a7c4..27a187755 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.403", + "version": "9.0.101", "rollForward": "latestFeature" }, "msbuild-sdks": diff --git a/tooling b/tooling index 7e6206391..43e41c221 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 7e620639169a85e9430504563f00426ab39c85d2 +Subproject commit 43e41c2215b583ffcd54eb33954c79d069249b55 From 96578276d2af9734d052e4dbeb596ace8323d206 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 18:22:04 -0600 Subject: [PATCH 028/126] Fixed compilation conditionals --- .../src/GeneratedDependencyProperty.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs index e5dbcc3a5..0db11876a 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyProperty.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if WINDOWS_UWP || HAS_UNO -using DependencyProperty = Windows.UI.Xaml.DependencyProperty; -#else +#if WINAPPSDK using DependencyProperty = Microsoft.UI.Xaml.DependencyProperty; +#else +using DependencyProperty = Windows.UI.Xaml.DependencyProperty; #endif namespace CommunityToolkit.WinUI; From 10499acfaac028a128a34877b32478080e7af8e6 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 18:56:24 -0600 Subject: [PATCH 029/126] Temporarily limit WinUI and multitarget options in build matrix for Uno/Wasm compatibility --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1cc92604e..d3f758220 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,8 +60,8 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2, 3] - multitarget: ['uwp', 'wasdk', 'wasm', 'wpf', 'linuxgtk', 'macos', 'ios', 'android'] + winui: [2] # Temporary until we can get Uno/Wasm working + multitarget: ['uwp'] # Temporary until we can get Uno/Wasm working exclude: # WinUI 2 not supported on wasdk - winui: 2 From da2b2c325243f0b84d5d6d8663d1e7cb2f102946 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 19:05:26 -0600 Subject: [PATCH 030/126] Temporarily disable wasm-linux check, only build DependencyPropertyGenerator component --- .github/workflows/build.yml | 56 +++---------------------------------- 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3f758220..3054ae6ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,7 +135,7 @@ jobs: # Generate full solution with all projects (sample gallery heads, components, tests) - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests working-directory: ./ - run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -Components DependencyPropertyGenerator -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop # Build solution - name: MSBuild (With diagnostics) @@ -213,7 +213,7 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2, 3] + winui: [2] env: VERSION_PROPERTY: ${{ github.ref == 'refs/heads/main' && format('build.{0}', github.run_number) || format('pull-{0}.{1}', github.event.number, github.run_number) }} @@ -269,7 +269,7 @@ jobs: # Build and pack component nupkg - name: Build and pack component packages - run: ./tooling/Build-Toolkit-Components.ps1 -MultiTargets all -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release + run: ./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -MultiTargets uwp -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release - name: Validate package names if: ${{ env.VERSION_PROPERTY != '' }} @@ -306,52 +306,4 @@ jobs: if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} with: name: build-logs-winui${{ matrix.winui }} - path: ./*.*log - - wasm-linux: - runs-on: ubuntu-latest - env: - HEADS_DIRECTORY: tooling/ProjectHeads - - steps: - - name: Install .NET SDK v${{ env.DOTNET_VERSION }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - - - name: .NET Info (if diagnostics) - if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} - run: dotnet --info - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout Repository - uses: actions/checkout@v4 - with: - submodules: recursive - - # Restore Tools from Manifest list in the Repository - - name: Restore dotnet tools - run: dotnet tool restore - - - name: Generate solution - shell: pwsh - working-directory: ./ - run: ./tooling/GenerateAllSolution.ps1${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} - - - name: Install ninja for WASM native dependencies - run: sudo apt-get install ninja-build - - # Issue with Comment Links currently, see: https://github.com/mrlacey/CommentLinks/issues/38 - # See launch.json configuration file for analogous command we're emulating here to build LINK: ../../.vscode/launch.json:CommunityToolkit.App.Wasm.csproj - - name: dotnet build - working-directory: ./${{ env.HEADS_DIRECTORY }}/AllComponents/Wasm/ - run: dotnet build /r /bl /p:UnoSourceGeneratorUseGenerationHost=true /p:UnoSourceGeneratorUseGenerationController=false - - # TODO: Do we want to run tests here? Can we do that on linux easily? - - - name: Artifact - Diagnostic Logs - uses: actions/upload-artifact@v4 - if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} - with: - name: linux-logs - path: ./**/*.*log + path: ./*.*log \ No newline at end of file From cf30a6452eeeb753c69a6f953c7712ee0ddb998c Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 19:24:50 -0600 Subject: [PATCH 031/126] Adjust CI to only build DependencyPropertyGenerator --- .github/workflows/build.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3054ae6ca..e60b97202 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -134,23 +134,24 @@ jobs: # Generate full solution with all projects (sample gallery heads, components, tests) - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests - working-directory: ./ - run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -Components DependencyPropertyGenerator -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + working-directory: ./components/DependencyPropertyGenerator + run: powershell -version 5.1 -command "./GenerateSingleSampleHeads.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop # Build solution - name: MSBuild (With diagnostics) if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} + working-directory: ./components/DependencyPropertyGenerator run: > msbuild.exe /restore /nowarn:MSB4011 /p:Configuration=Release /m ${{ env.ENABLE_DIAGNOSTICS == 'true' && '/bl' || '' }} /v:${{ env.MSBUILD_VERBOSITY }} - CommunityToolkit.AllComponents.sln - name: MSBuild if: ${{ env.ENABLE_DIAGNOSTICS == 'false' }} - run: msbuild.exe CommunityToolkit.AllComponents.sln /restore /nowarn:MSB4011 -p:Configuration=Release + working-directory: ./components/DependencyPropertyGenerator + run: msbuild.exe /restore /nowarn:MSB4011 -p:Configuration=Release # Run tests - name: Setup VSTest Path From ec018efc5411fae39495bb765a18a27c37903ddf Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 19:38:41 -0600 Subject: [PATCH 032/126] Enable WinUI 3, fix GenerateSingleSampleHeads script invocation --- .github/workflows/build.yml | 10 +++++----- tooling | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e60b97202..0c59212de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,8 +60,8 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2] # Temporary until we can get Uno/Wasm working - multitarget: ['uwp'] # Temporary until we can get Uno/Wasm working + winui: [2, 3] # Temporary until we can get Uno/Wasm working + multitarget: ['uwp', 'wasdk'] # Temporary until we can get Uno/Wasm working exclude: # WinUI 2 not supported on wasdk - winui: 2 @@ -135,7 +135,7 @@ jobs: # Generate full solution with all projects (sample gallery heads, components, tests) - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests working-directory: ./components/DependencyPropertyGenerator - run: powershell -version 5.1 -command "./GenerateSingleSampleHeads.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + run: powershell -version 5.1 -command "../../tooling/ProjectHeads/GenerateSingleSampleHeads.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop # Build solution - name: MSBuild (With diagnostics) @@ -214,7 +214,7 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2] + winui: [2, 3] # Temporary until we can get Uno/Wasm working env: VERSION_PROPERTY: ${{ github.ref == 'refs/heads/main' && format('build.{0}', github.run_number) || format('pull-{0}.{1}', github.event.number, github.run_number) }} @@ -270,7 +270,7 @@ jobs: # Build and pack component nupkg - name: Build and pack component packages - run: ./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -MultiTargets uwp -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release + run: ./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -MultiTargets uwp,wasdk -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release - name: Validate package names if: ${{ env.VERSION_PROPERTY != '' }} diff --git a/tooling b/tooling index 43e41c221..4d331c6b2 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 43e41c2215b583ffcd54eb33954c79d069249b55 +Subproject commit 4d331c6b24e258afcd42d28ebf63bea781744541 From c937c4c21e0073d804a6364e91ee1e7af7aaf97a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 17:39:15 -0800 Subject: [PATCH 033/126] Switch generator to WinAppSDK for now --- ...tyToolkit.DependencyPropertyGenerator.SourceGenerators.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 32c4c1233..6b470e010 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -4,7 +4,6 @@ enable true true - $(DefineConstants);WINDOWS_UWP $(NoWarn);IDE0130 From 9acc0ce14e80eaf59d1ce56ddc6aefa9e1f3e583 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 4 Dec 2024 19:53:25 -0600 Subject: [PATCH 034/126] Temp: Refactor build workflow to use Build-Toolkit-Components script instead of generating solution --- .github/workflows/build.yml | 38 +------------------------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c59212de..0679c7d2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -134,43 +134,7 @@ jobs: # Generate full solution with all projects (sample gallery heads, components, tests) - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests - working-directory: ./components/DependencyPropertyGenerator - run: powershell -version 5.1 -command "../../tooling/ProjectHeads/GenerateSingleSampleHeads.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop - - # Build solution - - name: MSBuild (With diagnostics) - if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} - working-directory: ./components/DependencyPropertyGenerator - run: > - msbuild.exe /restore /nowarn:MSB4011 - /p:Configuration=Release - /m - ${{ env.ENABLE_DIAGNOSTICS == 'true' && '/bl' || '' }} - /v:${{ env.MSBUILD_VERBOSITY }} - - - name: MSBuild - if: ${{ env.ENABLE_DIAGNOSTICS == 'false' }} - working-directory: ./components/DependencyPropertyGenerator - run: msbuild.exe /restore /nowarn:MSB4011 -p:Configuration=Release - - # Run tests - - name: Setup VSTest Path - uses: darenm/setup-vstest@3a16d909a1f3bbc65b52f8270d475d905e7d3e44 - - - name: Install Testspace Module - uses: testspace-com/setup-testspace@v1 - with: - domain: ${{ github.repository_owner }} - - - name: Run component tests against ${{ matrix.multitarget }} - if: ${{ matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk' }} - id: test-platform - run: vstest.console.exe ./tooling/**/CommunityToolkit.Tests.${{ matrix.multitarget }}.build.appxrecipe /Framework:FrameworkUap10 /logger:"trx;LogFileName=${{ matrix.multitarget }}.trx" /Blame - - - name: Create test reports - run: | - testspace '[${{ matrix.multitarget }}]./TestResults/*.trx' - if: ${{ (matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk') && (steps.test-generator.conclusion == 'success' || steps.test-platform.conclusion == 'success') }} + run: powershell -version 5.1 -command "./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -Release -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop - name: Artifact - Diagnostic Logs uses: actions/upload-artifact@v4 From 05edd936c8b3c6ea660a82f74175ea9288d9939a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 18:34:44 -0800 Subject: [PATCH 035/126] Fix condition for local caching in generator --- .../DependencyPropertyGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index d82418f23..5ebe11b8a 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -47,7 +47,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // This generator requires C# preview to be used (due to the use of the 'field' keyword). // The 'field' keyword is actually only used when local caching is enabled, so filter to that. - if (!isLocalCachingEnabled && !context.SemanticModel.Compilation.IsLanguageVersionPreview()) + if (isLocalCachingEnabled && !context.SemanticModel.Compilation.IsLanguageVersionPreview()) { return null; } From c7ab831cbdce705cb18a0362d9d57217b1a7d4d5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 19:53:23 -0800 Subject: [PATCH 036/126] Define 'DependencyPropertyGeneratorUseWindowsUIXaml' --- ...oolkit.WinUI.DependencyPropertyGenerator.targets | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index 3a93502bc..0e057128c 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -1,7 +1,18 @@ + + + true + false + + + + + + + - + $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML From aff5de987dde2467d27fd42c69c4098eecf1de9b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 21:12:48 -0800 Subject: [PATCH 037/126] Generalize .dll reference check --- ...it.WinUI.DependencyPropertyGenerator.targets | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index 0e057128c..29f744307 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -16,27 +16,22 @@ $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML - + - + + false - true + true + true - + Date: Wed, 4 Dec 2024 21:16:58 -0800 Subject: [PATCH 038/126] Add 'ForAttributeWithMetadataNameAndOptions' --- ...eneratorInitializationContextExtensions.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs new file mode 100644 index 000000000..7c3169d03 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs @@ -0,0 +1,67 @@ +// 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.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// +/// +/// The original value. +/// The original value. +internal readonly struct GeneratorAttributeSyntaxContextWithOptions( + GeneratorAttributeSyntaxContext syntaxContext, + AnalyzerConfigOptions globalOptions) +{ + /// + public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode; + + /// + public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol; + + /// + public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel; + + /// + public ImmutableArray Attributes { get; } = syntaxContext.Attributes; + + /// + public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions; +} + +/// +/// Extension methods for . +/// +internal static class IncrementalGeneratorInitializationContextExtensions +{ + /// + public static IncrementalValuesProvider ForAttributeWithMetadataNameAndOptions( + this IncrementalGeneratorInitializationContext context, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { + // Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly + IncrementalValuesProvider syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName, + predicate, + static (context, token) => context); + + // Do the same for the analyzer config options + IncrementalValueProvider configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions); + + // Merge the two and invoke the provided transform on these two values. Neither value + // is equatable, meaning the pipeline will always re-run until this point. This is + // intentional: we don't want any symbols or other expensive objects to be kept alive + // across incremental steps, especially if they could cause entire compilations to be + // rooted, which would significantly increase memory use and introduce more GC pauses. + // In this specific case, flowing non equatable values in a pipeline is therefore fine. + return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token)); + } +} From 061f74e46bb7c7b3c3c3cc86991791865c6985a8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 21:56:03 -0800 Subject: [PATCH 039/126] Add 'AnalyzerConfigOptionsExtensions' --- .../AnalyzerConfigOptionsExtensions.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs new file mode 100644 index 000000000..4737574d9 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AnalyzerConfigOptionsExtensions.cs @@ -0,0 +1,46 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class AnalyzerConfigOptionsExtensions +{ + /// + /// Gets the boolean value of a given MSBuild property from an input instance. + /// + /// The input instance. + /// The name of the target MSBuild property. + /// The default value to return if the property is not found or cannot be parsed. + /// The value of the target MSBuild property. + public static bool GetMSBuildBooleanPropertyValue(this AnalyzerConfigOptions options, string propertyName, bool defaultValue = false) + { + if (options.TryGetMSBuildStringPropertyValue(propertyName, out string? propertyValue)) + { + if (bool.TryParse(propertyValue, out bool booleanPropertyValue)) + { + return booleanPropertyValue; + } + } + + return defaultValue; + } + + /// + /// Tries to get a value of a given MSBuild property from an input instance. + /// + /// The input instance. + /// The name of the target MSBuild property. + /// The resulting property value. + /// Whether the property value was retrieved.. + public static bool TryGetMSBuildStringPropertyValue(this AnalyzerConfigOptions options, string propertyName, [NotNullWhen(true)] out string? propertyValue) + { + return options.TryGetValue($"build_property.{propertyName}", out propertyValue); + } +} From 6af9be8364414a0bf2876d2dc5ce64d6896ece90 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 4 Dec 2024 21:56:21 -0800 Subject: [PATCH 040/126] Enable XAML option for generators/analyzers --- .../Constants/WellKnownPropertyNames.cs | 16 +++++ .../Constants/WellKnownTypeNames.cs | 66 ++++++++++++++----- .../DependencyPropertyGenerator.Execute.cs | 48 +++++++------- .../DependencyPropertyGenerator.cs | 16 +++-- ...dPropertyConflictingDeclarationAnalyzer.cs | 5 +- ...opertyContainingTypeDeclarationAnalyzer.cs | 5 +- .../Models/DependencyPropertyInfo.cs | 4 +- .../Models/TypedConstantInfo.cs | 5 +- 8 files changed, 114 insertions(+), 51 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownPropertyNames.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownPropertyNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownPropertyNames.cs new file mode 100644 index 000000000..7d88ca86a --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownPropertyNames.cs @@ -0,0 +1,16 @@ +// 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. + +namespace CommunityToolkit.GeneratedDependencyProperty.Constants; + +/// +/// The well known names for properties used by source generators and analyzers. +/// +internal static class WellKnownPropertyNames +{ + /// + /// The MSBuild property to control the XAML mode. + /// + public const string DependencyPropertyGeneratorUseWindowsUIXaml = nameof(DependencyPropertyGeneratorUseWindowsUIXaml); +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs index 60194c1db..d200ed859 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs @@ -15,38 +15,72 @@ internal static class WellKnownTypeNames public const string GeneratedDependencyPropertyAttribute = "CommunityToolkit.WinUI.GeneratedDependencyPropertyAttribute"; /// - /// The fully qualified type name for the GeneratedDependencyProperty type. + /// The fully qualified name for the GeneratedDependencyProperty type. /// public const string GeneratedDependencyProperty = "CommunityToolkit.WinUI.GeneratedDependencyProperty"; /// - /// The fully qualified type name for the XAML namespace. + /// The fully qualified name for the Windows.UI.Xaml namespace. /// - public const string XamlNamespace = -#if WINDOWS_UWP - "Windows.UI.Xaml"; + public const string WindowsUIXamlNamespace = "Windows.UI.Xaml"; -#else - "Microsoft.UI.Xaml"; -#endif + /// + /// The fully qualified name for the Microsoft.UI.Xaml namespace. + /// + public const string MicrosoftUIXamlNamespace = "Microsoft.UI.Xaml"; + + /// + /// Gets the fully qualified name for theXAML namespace. + /// + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. + public static string XamlNamespace(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? WindowsUIXamlNamespace + : MicrosoftUIXamlNamespace; + } /// - /// The fully qualified type name for the DependencyObject type. + /// Gets the fully qualified type name for the DependencyObject type for a given XAML mode. /// - public const string DependencyObject = $"{XamlNamespace}.{nameof(DependencyObject)}"; + /// + public static string DependencyObject(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.{nameof(DependencyObject)}" + : $"{MicrosoftUIXamlNamespace}.{nameof(DependencyObject)}"; + } /// - /// The fully qualified type name for the DependencyProperty type. + /// Gets the fully qualified type name for the DependencyProperty type. /// - public const string DependencyProperty = $"{XamlNamespace}.{nameof(DependencyProperty)}"; + /// + public static string DependencyProperty(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.{nameof(DependencyProperty)}" + : $"{MicrosoftUIXamlNamespace}.{nameof(DependencyProperty)}"; + } /// - /// The fully qualified type name for the DependencyPropertyChangedEventArgs type. + /// Gets the fully qualified type name for the DependencyPropertyChangedEventArgs type. /// - public const string DependencyPropertyChangedEventArgs = $"{XamlNamespace}.{nameof(DependencyPropertyChangedEventArgs)}"; + /// + public static string DependencyPropertyChangedEventArgs(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.{nameof(DependencyPropertyChangedEventArgs)}" + : $"{MicrosoftUIXamlNamespace}.{nameof(DependencyPropertyChangedEventArgs)}"; + } /// - /// The fully qualified type name for the PropertyMetadata type. + /// Gets the fully qualified type name for the PropertyMetadata type. /// - public const string PropertyMetadata = $"{XamlNamespace}.{nameof(PropertyMetadata)}"; + /// + public static string PropertyMetadata(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.{nameof(PropertyMetadata)}" + : $"{MicrosoftUIXamlNamespace}.{nameof(PropertyMetadata)}"; + } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index f14961453..b94b1e752 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -31,11 +31,6 @@ private static partial class Execute /// private static readonly TypedConstantInfo.Null NullInfo = new(); - /// - /// Placeholder for the unset value of a given property type. - /// - private static readonly TypedConstantInfo.UnsetValue UnsetValueInfo = new(); - /// /// Generates the sources for the embedded types, for PrivateAssets="all" scenarios. /// @@ -109,8 +104,9 @@ public static bool IsCandidateSyntaxValid(SyntaxNode node, CancellationToken tok /// Checks whether an input symbol is a candidate property declaration for the generator. /// /// The input symbol to check. + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// Whether is a candidate property declaration. - public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol) + public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol, bool useWindowsUIXaml) { // Ensure that the property declaration is a partial definition with no implementation if (propertySymbol is not { IsPartialDefinition: true, PartialImplementationPart: null }) @@ -131,7 +127,7 @@ public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol) } // Ensure that the containing type derives from 'DependencyObject' - if (!typeSymbol.InheritsFromFullyQualifiedMetadataName(WellKnownTypeNames.DependencyObject)) + if (!typeSymbol.InheritsFromFullyQualifiedMetadataName(WellKnownTypeNames.DependencyObject(useWindowsUIXaml))) { return false; } @@ -143,7 +139,7 @@ public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol) { bool propertyTypeWouldCauseConflicts = propertySymbol.Type.SpecialType == SpecialType.System_Object || - propertySymbol.Type.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs); + propertySymbol.Type.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs(useWindowsUIXaml)); return !propertyTypeWouldCauseConflicts; } @@ -211,12 +207,14 @@ public static bool TryGetAccessibilityModifiers( /// The input that triggered the annotation. /// The input instance. /// The for the current compilation. + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// The used to cancel the operation, if needed. /// The default value to use to initialize the generated property. public static TypedConstantInfo GetDefaultValue( AttributeData attributeData, IPropertySymbol propertySymbol, SemanticModel semanticModel, + bool useWindowsUIXaml, CancellationToken token) { // First, check whether the default value is explicitly set or not @@ -245,7 +243,7 @@ public static TypedConstantInfo GetDefaultValue( // Last step: we want to validate that the reference is actually to the special placeholder if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName(WellKnownTypeNames.GeneratedDependencyProperty)) { - return UnsetValueInfo; + return new TypedConstantInfo.UnsetValue(useWindowsUIXaml); } } } @@ -281,8 +279,9 @@ public static bool IsLocalCachingEnabled(AttributeData attributeData) /// Checks whether the generated code has to register the property changed callback with WinRT. /// /// The input instance to process. + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// Whether the generated should register the property changed callback. - public static bool IsPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol) + public static bool IsPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol, bool useWindowsUIXaml) { // Check for any 'OnChanged' methods foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers($"On{propertySymbol.Name}PropertyChanged")) @@ -296,7 +295,7 @@ public static bool IsPropertyChangedCallbackImplemented(IPropertySymbol property // There might be other property changed callback methods when field caching is enabled, or in other scenarios. // Because the callback method existing adds overhead (since we have to register it with WinRT), we want to // avoid false positives. To do that, we check that the parameter type is exactly the one we need. - if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs)) + if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs(useWindowsUIXaml))) { return true; } @@ -309,8 +308,9 @@ public static bool IsPropertyChangedCallbackImplemented(IPropertySymbol property /// Checks whether the generated code has to register the shared property changed callback with WinRT. /// /// The input instance to process. + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// Whether the generated should register the shared property changed callback. - public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol) + public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol propertySymbol, bool useWindowsUIXaml) { // Check for any 'OnPropertyChanged' methods foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers("OnPropertyChanged")) @@ -322,7 +322,7 @@ public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol pr } // Also same actual check as above - if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs)) + if (argsType.HasFullyQualifiedMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs(useWindowsUIXaml))) { return true; } @@ -385,36 +385,36 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { DefaultValue: TypedConstantInfo.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } - => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue})", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue})", // Codegen for legacy UWP { IsNet8OrGreater: false } => propertyInfo switch { { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } - => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } - => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: true } - => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), }, // Codegen for .NET 8 or greater { DefaultValue: TypedConstantInfo.Null } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) - => $"new global::{WellKnownTypeNames.PropertyMetadata}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", { DefaultValue: { } defaultValue } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) - => $"new global::{WellKnownTypeNames.PropertyMetadata}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), }; writer.WriteLine($$""" /// - /// The backing instance for . + /// The backing instance for . /// """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); writer.WriteLine($$""" - public static readonly global::{{WellKnownTypeNames.DependencyProperty}} {{propertyInfo.PropertyName}}Property = global::{{WellKnownTypeNames.DependencyProperty}}.Register( + public static readonly global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}} {{propertyInfo.PropertyName}}Property = global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}}.Register( name: "{{propertyInfo.PropertyName}}", propertyType: typeof({{propertyInfo.TypeName}}), ownerType: typeof({{typeQualifiedName}}), @@ -650,7 +650,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) writer.WriteLine($""" /// Executes the logic for when has just changed. /// Event data that is issued by any event that tracks changes to the effective value of this property. - /// This method is invoked by the infrastructure, after the value of is changed. + /// This method is invoked by the infrastructure, after the value of is changed. """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); writer.WriteLine($"partial void On{propertyInfo.PropertyName}PropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); @@ -661,7 +661,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) writer.WriteLine($""" /// Executes the logic for when any dependency property has just changed. /// Event data that is issued by any event that tracks changes to the effective value of this property. - /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); writer.WriteLine($"partial void OnPropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); @@ -700,7 +700,7 @@ public static bool RequiresAdditionalTypes(EquatableArray propertyInfos, IndentedTextWriter writer) { writer.WriteLine("using global::System.Runtime.CompilerServices;"); - writer.WriteLine($"using global::{WellKnownTypeNames.XamlNamespace};"); + writer.WriteLine($"using global::{WellKnownTypeNames.XamlNamespace(propertyInfos[0].UseWindowsUIXaml)};"); writer.WriteLine(); writer.WriteLine($$""" /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index 5ebe11b8a..2e21759c1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -31,8 +31,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Get the info on all dependency properties to generate IncrementalValuesProvider propertyInfo = - context.SyntaxProvider - .ForAttributeWithMetadataName( + context.ForAttributeWithMetadataNameAndOptions( WellKnownTypeNames.GeneratedDependencyPropertyAttribute, Execute.IsCandidateSyntaxValid, static (context, token) => @@ -60,8 +59,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return null; } + // Get the XAML mode to use + bool useWindowsUIXaml = context.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml); + // Do an initial filtering on the symbol as well - if (!Execute.IsCandidateSymbolValid(propertySymbol)) + if (!Execute.IsCandidateSymbolValid(propertySymbol, useWindowsUIXaml)) { return null; } @@ -87,8 +89,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); bool isRequired = Execute.IsRequiredProperty(propertySymbol); - bool isPropertyChangedCallbackImplemented = Execute.IsPropertyChangedCallbackImplemented(propertySymbol); - bool isSharedPropertyChangedCallbackImplemented = Execute.IsSharedPropertyChangedCallbackImplemented(propertySymbol); + bool isPropertyChangedCallbackImplemented = Execute.IsPropertyChangedCallbackImplemented(propertySymbol, useWindowsUIXaml); + bool isSharedPropertyChangedCallbackImplemented = Execute.IsSharedPropertyChangedCallbackImplemented(propertySymbol, useWindowsUIXaml); bool isNet8OrGreater = !context.SemanticModel.Compilation.IsWindowsRuntimeApplication(); token.ThrowIfCancellationRequested(); @@ -103,6 +105,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.Attributes[0], propertySymbol, context.SemanticModel, + useWindowsUIXaml, token); // The 'UnsetValue' can only be used when local caching is disabled @@ -132,7 +135,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IsLocalCachingEnabled: isLocalCachingEnabled, IsPropertyChangedCallbackImplemented: isPropertyChangedCallbackImplemented, IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented, - IsNet8OrGreater: isNet8OrGreater); + IsNet8OrGreater: isNet8OrGreater, + UseWindowsUIXaml: useWindowsUIXaml); }) .WithTrackingName(WellKnownTrackingNames.Execute) .Where(static item => item is not null)!; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs index 0c204d0c5..5bf8bf9ac 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs @@ -28,11 +28,14 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { + // Get the XAML mode to use + bool useWindowsUIXaml = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml); + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Get the 'DependencyPropertyChangedEventArgs' symbol - if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs) is not { } dependencyPropertyChangedEventArgsSymbol) + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyPropertyChangedEventArgs(useWindowsUIXaml)) is not { } dependencyPropertyChangedEventArgsSymbol) { return; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs index af90b2470..10fe9967c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs @@ -28,11 +28,14 @@ public override void Initialize(AnalysisContext context) context.RegisterCompilationStartAction(static context => { + // Get the XAML mode to use + bool useWindowsUIXaml = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml); + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); // Get the 'DependencyObject' symbol - if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject) is not { } dependencyObjectSymbol) + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject(useWindowsUIXaml)) is not { } dependencyObjectSymbol) { return; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs index 3952dd95d..38a5af29d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs @@ -23,6 +23,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// Indicates whether the WinRT-based property changed callback is implemented. /// Indicates whether the WinRT-based shared property changed callback is implemented. /// Indicates whether the current target is .NET 8 or greater. +/// Whether to use the UWP XAML or WinUI 3 XAML namespaces. internal sealed record DependencyPropertyInfo( HierarchyInfo Hierarchy, string PropertyName, @@ -37,4 +38,5 @@ internal sealed record DependencyPropertyInfo( bool IsLocalCachingEnabled, bool IsPropertyChangedCallbackImplemented, bool IsSharedPropertyChangedCallbackImplemented, - bool IsNet8OrGreater); + bool IsNet8OrGreater, + bool UseWindowsUIXaml); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index d06ef9618..bbc5f5710 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -176,12 +176,13 @@ public override string ToString() /// /// A type representing the special unset value. /// - public sealed record UnsetValue : TypedConstantInfo + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. + public sealed record UnsetValue(bool UseWindowsUIXaml) : TypedConstantInfo { /// public override string ToString() { - return $"global::{WellKnownTypeNames.DependencyProperty}.UnsetValue"; + return $"global::{WellKnownTypeNames.DependencyProperty(UseWindowsUIXaml)}.UnsetValue"; } } } From c100c0eb793751562f8f4ac4298c213031fcabae Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Dec 2024 08:20:52 -0800 Subject: [PATCH 041/126] Enable Windows.UI.Xaml for legacy UWP --- .../CommunityToolkit.WinUI.DependencyPropertyGenerator.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index 29f744307..7806f34d3 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -2,6 +2,7 @@ + true true false From 9439f37e2126df3467985ce4ef6132c4b8ab287f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Dec 2024 18:54:39 -0800 Subject: [PATCH 042/126] Add 'SyntaxKind' extensions --- .../Extensions/SyntaxKindExtensions.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxKindExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxKindExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxKindExtensions.cs new file mode 100644 index 000000000..6b966edaf --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxKindExtensions.cs @@ -0,0 +1,39 @@ +// 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.Collections.Immutable; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis.CSharp; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// A with some extension methods for C# syntax kinds. +/// +internal static partial class SyntaxKindExtensions +{ + /// + /// Converts an of values to one of their underlying type. + /// + /// The input value. + /// The resulting of values. + public static ImmutableArray AsUnderlyingType(this ImmutableArray array) + { + ushort[]? underlyingArray = (ushort[]?)(object?)Unsafe.As, SyntaxKind[]?>(ref array); + + return Unsafe.As>(ref underlyingArray); + } + + /// + /// Converts an of values to one of their real type. + /// + /// The input value. + /// The resulting of values. + public static ImmutableArray AsSyntaxKindArray(this ImmutableArray array) + { + SyntaxKind[]? typedArray = (SyntaxKind[]?)(object?)Unsafe.As, ushort[]?>(ref array); + + return Unsafe.As>(ref typedArray); + } +} From eb41e2869d1e858cd6fd0b73400fd08720e101c3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Dec 2024 19:27:46 -0800 Subject: [PATCH 043/126] Add support for more modifiers, bug fixes --- .../DependencyPropertyGenerator.Execute.cs | 59 ++++++++++++++----- .../DependencyPropertyGenerator.cs | 9 ++- .../Extensions/AccessibilityExtensions.cs | 32 ---------- .../Models/DependencyPropertyInfo.cs | 5 +- 4 files changed, 55 insertions(+), 50 deletions(-) delete mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index b94b1e752..3501de65c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; @@ -147,6 +148,37 @@ public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol, bool u return true; } + /// + /// Gathers all allowed property modifiers that should be forwarded to the generated property. + /// + /// The input node. + /// The returned set of property modifiers, if any. + public static ImmutableArray GetPropertyModifiers(PropertyDeclarationSyntax node) + { + // We only allow a subset of all possible modifiers (aside from the accessibility modifiers) + ReadOnlySpan candidateKinds = + [ + SyntaxKind.NewKeyword, + SyntaxKind.VirtualKeyword, + SyntaxKind.SealedKeyword, + SyntaxKind.OverrideKeyword, + SyntaxKind.RequiredKeyword + ]; + + using ImmutableArrayBuilder builder = new(); + + // Track all modifiers from the allowed set on the input property declaration + foreach (SyntaxKind kind in candidateKinds) + { + if (node.Modifiers.Any(kind)) + { + builder.Add(kind); + } + } + + return builder.ToImmutable(); + } + /// /// Tries to get the accessibility of the property and accessors, if possible. /// @@ -331,16 +363,6 @@ public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol pr return false; } - /// - /// Checks whether an input property is required. - /// - /// The input instance to process. - /// Whether is required. - public static bool IsRequiredProperty(IPropertySymbol propertySymbol) - { - return propertySymbol.IsRequired; - } - /// /// Writes all implementations of partial dependency property declarations. /// @@ -367,7 +389,7 @@ static string GetOldValueTypeNameAsNullable(DependencyPropertyInfo propertyInfo) // Helper to get the accessibility with a trailing space static string GetExpressionWithTrailingSpace(Accessibility accessibility) { - return accessibility.GetExpression() switch + return SyntaxFacts.GetText(accessibility) switch { { Length: > 0 } expression => expression + " ", _ => "" @@ -428,11 +450,20 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { string oldValueTypeNameAsNullable = GetOldValueTypeNameAsNullable(propertyInfo); + // Declare the property writer.WriteLine(skipIfPresent: true); writer.WriteLine("/// "); writer.WriteGeneratedAttributes(GeneratorName); writer.Write(GetExpressionWithTrailingSpace(propertyInfo.DeclaredAccessibility)); - writer.WriteIf(propertyInfo.IsRequired, "required "); + + // Add all gathered modifiers + foreach (SyntaxKind modifier in propertyInfo.PropertyModifiers.AsImmutableArray().AsSyntaxKindArray()) + { + writer.Write($"{SyntaxFacts.GetText(modifier)} "); + } + + // The 'partial' modifier always goes last, right before the property type and the property name. + // We will never have the 'partial' modifier in the set of property modifiers processed above. writer.WriteLine($"partial {propertyInfo.TypeNameWithNullabilityAnnotations} {propertyInfo.PropertyName}"); using (writer.WriteBlock()) @@ -653,7 +684,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) /// This method is invoked by the infrastructure, after the value of is changed. """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); - writer.WriteLine($"partial void On{propertyInfo.PropertyName}PropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); + writer.WriteLine($"partial void On{propertyInfo.PropertyName}PropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs(propertyInfo.UseWindowsUIXaml)} e);"); } // OnPropertyChanged, for the shared property metadata callback @@ -664,7 +695,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); - writer.WriteLine($"partial void OnPropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs} e);"); + writer.WriteLine($"partial void OnPropertyChanged(global::{WellKnownTypeNames.DependencyPropertyChangedEventArgs(propertyInfos[0].UseWindowsUIXaml)} e);"); } /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index 2e21759c1..ff70652e7 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -2,6 +2,7 @@ // 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.Collections.Immutable; using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using CommunityToolkit.GeneratedDependencyProperty.Helpers; @@ -70,6 +71,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); + // Get all additional modifiers for the property + ImmutableArray propertyModifiers = Execute.GetPropertyModifiers((PropertyDeclarationSyntax)context.TargetNode); + + token.ThrowIfCancellationRequested(); + // Get the accessibility values, if the property is valid if (!Execute.TryGetAccessibilityModifiers( node: (PropertyDeclarationSyntax)context.TargetNode, @@ -88,7 +94,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); - bool isRequired = Execute.IsRequiredProperty(propertySymbol); bool isPropertyChangedCallbackImplemented = Execute.IsPropertyChangedCallbackImplemented(propertySymbol, useWindowsUIXaml); bool isSharedPropertyChangedCallbackImplemented = Execute.IsSharedPropertyChangedCallbackImplemented(propertySymbol, useWindowsUIXaml); bool isNet8OrGreater = !context.SemanticModel.Compilation.IsWindowsRuntimeApplication(); @@ -124,6 +129,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return new DependencyPropertyInfo( Hierarchy: hierarchyInfo, PropertyName: propertySymbol.Name, + PropertyModifiers: propertyModifiers.AsUnderlyingType(), DeclaredAccessibility: declaredAccessibility, GetterAccessibility: getterAccessibility, SetterAccessibility: setterAccessibility, @@ -131,7 +137,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) TypeNameWithNullabilityAnnotations: typeNameWithNullabilityAnnotations, DefaultValue: defaultValue, IsReferenceTypeOrUnconstraindTypeParameter: isReferenceTypeOrUnconstraindTypeParameter, - IsRequired: isRequired, IsLocalCachingEnabled: isLocalCachingEnabled, IsPropertyChangedCallbackImplemented: isPropertyChangedCallbackImplemented, IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented, diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs deleted file mode 100644 index f7154e446..000000000 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/AccessibilityExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// 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; - -namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; - -/// -/// Extension methods for the type. -/// -internal static class AccessibilityExtensions -{ - /// - /// Gets the expression for a given value. - /// - /// The input value. - /// The expression for . - public static string GetExpression(this Accessibility accessibility) - { - return accessibility switch - { - Accessibility.Private => "private", - Accessibility.ProtectedAndInternal => "private protected", - Accessibility.Protected => "protected", - Accessibility.Internal => "internal", - Accessibility.ProtectedOrInternal => "protected internal", - Accessibility.Public => "public", - _ => "" - }; - } -} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs index 38a5af29d..363ea6516 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.GeneratedDependencyProperty.Helpers; using Microsoft.CodeAnalysis; namespace CommunityToolkit.GeneratedDependencyProperty.Models; @@ -11,6 +12,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// /// The hierarchy info for the containing type. /// The property name. +/// The list of additional modifiers for the property (they are values). /// The accessibility of the property, if available. /// The accessibility of the accessor, if available. /// The accessibility of the accessor, if available. @@ -18,7 +20,6 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// The type name for the generated property, including nullability annotations. /// The default value to set the generated property to. /// Indicates whether the property is of a reference type or an unconstrained type parameter. -/// Whether or not the generated property should be marked as required. /// Indicates whether local caching should be used for the property value. /// Indicates whether the WinRT-based property changed callback is implemented. /// Indicates whether the WinRT-based shared property changed callback is implemented. @@ -27,6 +28,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; internal sealed record DependencyPropertyInfo( HierarchyInfo Hierarchy, string PropertyName, + EquatableArray PropertyModifiers, Accessibility DeclaredAccessibility, Accessibility GetterAccessibility, Accessibility SetterAccessibility, @@ -34,7 +36,6 @@ internal sealed record DependencyPropertyInfo( string TypeNameWithNullabilityAnnotations, TypedConstantInfo DefaultValue, bool IsReferenceTypeOrUnconstraindTypeParameter, - bool IsRequired, bool IsLocalCachingEnabled, bool IsPropertyChangedCallbackImplemented, bool IsSharedPropertyChangedCallbackImplemented, From 1b40cafb8aa98c8ac1922b5af0bc8392061581d9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Dec 2024 19:32:27 -0800 Subject: [PATCH 044/126] Add unit tests for more modifiers --- .../Test_DependencyPropertyGenerator.cs | 362 +++++++++++++++++- 1 file changed, 361 insertions(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 76587fa83..8cdace498 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -1648,7 +1648,7 @@ public partial string? Name } [TestMethod] - public void SingleProperty_String_WithNoCaching_IsRequired() + public void SingleProperty_String_WithNoCaching_Required() { const string source = """ using Windows.UI.Xaml; @@ -1763,6 +1763,366 @@ public required partial string Name CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); } + [TestMethod] + public void SingleProperty_String_WithNoCaching_New() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public class BaseControl : DependencyObject + { + public new string Name { get; set; } + } + + public partial class MyControl : BaseControl + { + [GeneratedDependencyProperty] + public new partial string Name { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public new partial string Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string __unboxedValue = (string)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void SingleProperty_String_WithNoCaching_Virtual() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public virtual partial string Name { get; set; } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public virtual partial string Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string __unboxedValue = (string)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + [DataRow("override")] + [DataRow("sealed override")] + public void SingleProperty_String_WithNoCaching_Override(string modifiers) + { + string source = $$""" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public class BaseControl : DependencyObject + { + public virtual string Name { get; set; } + } + + public partial class MyControl : BaseControl + { + [GeneratedDependencyProperty] + public {{modifiers}} partial string Name { get; set; } + } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public {{modifiers}} partial string Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string __unboxedValue = (string)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + [TestMethod] public void MultipleProperties_WithNoCaching_CorrectSpacing() { From e152803168b98321f24a38a79bfcdeee34a32014 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 5 Dec 2024 19:50:59 -0800 Subject: [PATCH 045/126] Add diagnostic and test for pointer types --- .../AnalyzerReleases.Shipped.md | 1 + .../DependencyPropertyGenerator.Execute.cs | 11 +++++- ...nvalidPropertySymbolDeclarationAnalyzer.cs | 16 ++++++-- .../Diagnostics/DiagnosticDescriptors.cs | 16 ++++++++ .../Test_Analyzers.cs | 38 +++++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md index a7615b210..d010b84f3 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md @@ -18,3 +18,4 @@ WCTDP0008 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera WCTDP0009 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | WCTDP0010 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | WCTDP0011 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | +WCTDP0012 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 3501de65c..9034585ed 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -115,8 +115,15 @@ public static bool IsCandidateSymbolValid(IPropertySymbol propertySymbol, bool u return false; } - // Also ignore all properties that have an invalid declaration - if (propertySymbol.IsStatic || propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly || propertySymbol.Type.IsRefLikeType) + // Also ignore all properties returning a byref-like value. We don't need to also + // check for ref values here, as that's already validated by the syntax filter. + if (propertySymbol.Type.IsRefLikeType) + { + return false; + } + + // Pointer types are never allowed + if (propertySymbol.Type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer) { return false; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs index 60a26e2b6..9284c6593 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySymbolDeclarationAnalyzer.cs @@ -22,7 +22,8 @@ public sealed class InvalidPropertySymbolDeclarationAnalyzer : DiagnosticAnalyze [ InvalidPropertyDeclarationIsNotIncompletePartialDefinition, InvalidPropertyDeclarationReturnsByRef, - InvalidPropertyDeclarationReturnsRefLikeType + InvalidPropertyDeclarationReturnsRefLikeType, + InvalidPropertyDeclarationReturnsPointerType ]; /// @@ -86,15 +87,22 @@ public override void Initialize(AnalysisContext context) attributeData.GetLocation(), propertySymbol)); } - - // Emit an error if the property type is a ref struct - if (propertySymbol.Type.IsRefLikeType) + else if (propertySymbol.Type.IsRefLikeType) { + // Emit an error if the property type is a ref struct context.ReportDiagnostic(Diagnostic.Create( InvalidPropertyDeclarationReturnsRefLikeType, attributeData.GetLocation(), propertySymbol)); } + else if (propertySymbol.Type.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer) + { + // Emit a diagnostic if the type is a pointer type + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationReturnsPointerType, + attributeData.GetLocation(), + propertySymbol)); + } }, SymbolKind.Property); }); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 213b3e8d7..7d3b61cb1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -153,4 +153,20 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValue' should do so with an expression of a type comparible with the property type. Alternatively, the 'Get(ref object)' method should be implemented to handle the type mismatch.", helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// Gets a for when [ObservableProperty] is used on a property that returns a pointer type. + /// + /// Format: "The property {0}.{1} returns a pointer or function pointer value ([ObservableProperty] must be used on properties of a non pointer-like type)". + /// + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationReturnsPointerType = new( + id: "WCTDP0012", + title: "Using [GeneratedDependencyProperty] on a property that returns pointer type", + messageFormat: """The property '{0}' cannot be used to generate a dependency property, as it returns a pointer value ([GeneratedDependencyProperty] must be used on properties returning a non pointer value)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] must not return a pointer value (only reference types and non byref-like types are supported).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index deaa19b7b..3a9c8e330 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -339,6 +339,44 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ReturnsPointerType_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public unsafe partial class MyControl : Control + { + [{|WCTDP0012:GeneratedDependencyProperty|}] + public partial int* {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertySymbolDeclarationAnalyzer_ReturnsFunctionPointerType_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public unsafe partial class MyControl : Control + { + [{|WCTDP0012:GeneratedDependencyProperty|}] + public partial delegate* unmanaged[Stdcall] {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + [TestMethod] public async Task InvalidPropertyContainingTypeDeclarationAnalyzer_NoAttribute_DoesNotWarn() { From 95590f06d14911bb6857ebecd02f6ef323b2c393 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 6 Dec 2024 10:19:39 -0800 Subject: [PATCH 046/126] Add 'DefaultValueCallback' property --- .../GeneratedDependencyPropertyAttribute.cs | 13 +++++++++++++ .../src/GeneratedDependencyPropertyAttribute.cs | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs index 752240f7e..c94eb6652 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs @@ -57,9 +57,22 @@ internal sealed class GeneratedDependencyPropertyAttribute : global::System.Attr /// /// To set the default value to , use . /// + /// + /// Using this property is mutually exclusive with . + /// /// public object? DefaultValue { get; init; } = null; + /// + /// Gets or sets the name of the method that will be invoked to produce the default value of the + /// property, for each instance of the containing type. The referenced method needs to return either + /// an , or a value of exactly the property type, and it needs to be parameterless. + /// + /// + /// Using this property is mutually exclusive with . + /// + public string? DefaultValueCallback { get; init; } = null; + /// /// Gets a value indicating whether or not property values should be cached locally, to improve performance. /// This allows completely skipping boxing (for value types) and all WinRT marshalling when setting properties. diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs index d7b016cfe..1510b9f33 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs @@ -48,9 +48,22 @@ public sealed class GeneratedDependencyPropertyAttribute : Attribute /// /// To set the default value to , use . /// + /// + /// Using this property is mutually exclusive with . + /// /// public object? DefaultValue { get; init; } = null; + /// + /// Gets or sets the name of the method that will be invoked to produce the default value of the + /// property, for each instance of the containing type. The referenced method needs to return either + /// an , or a value of exactly the property type, and it needs to be parameterless. + /// + /// + /// Using this property is mutually exclusive with . + /// + public string? DefaultValueCallback { get; init; } = null; + /// /// Gets a value indicating whether or not property values should be cached locally, to improve performance. /// This allows completely skipping boxing (for value types) and all WinRT marshalling when setting properties. From 22ab8688a02688d8812d9f6176386dad59e46703 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 6 Dec 2024 11:28:35 -0800 Subject: [PATCH 047/126] Add initial support for default value callbacks --- .../Constants/WellKnownTypeNames.cs | 11 +++ .../DependencyPropertyGenerator.Execute.cs | 68 +++++++++++++--- .../DependencyPropertyGenerator.cs | 4 +- .../Models/DependencyPropertyDefaultValue.cs | 77 +++++++++++++++++++ .../Models/DependencyPropertyInfo.cs | 2 +- .../Models/TypedConstantInfo.cs | 52 +++---------- 6 files changed, 161 insertions(+), 53 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs index d200ed859..da021cad5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs @@ -83,4 +83,15 @@ public static string PropertyMetadata(bool useWindowsUIXaml) ? $"{WindowsUIXamlNamespace}.{nameof(PropertyMetadata)}" : $"{MicrosoftUIXamlNamespace}.{nameof(PropertyMetadata)}"; } + + /// + /// Gets the fully qualified type name for the CreateDefaultValueCallback type. + /// + /// + public static string CreateDefaultValueCallback(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}" + : $"{MicrosoftUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}"; + } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 9034585ed..6dbbf15ff 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -30,7 +30,7 @@ private static partial class Execute /// /// Placeholder for . /// - private static readonly TypedConstantInfo.Null NullInfo = new(); + private static readonly DependencyPropertyDefaultValue.Null NullInfo = new(); /// /// Generates the sources for the embedded types, for PrivateAssets="all" scenarios. @@ -249,20 +249,64 @@ public static bool TryGetAccessibilityModifiers( /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// The used to cancel the operation, if needed. /// The default value to use to initialize the generated property. - public static TypedConstantInfo GetDefaultValue( + public static DependencyPropertyDefaultValue GetDefaultValue( AttributeData attributeData, IPropertySymbol propertySymbol, SemanticModel semanticModel, bool useWindowsUIXaml, CancellationToken token) { - // First, check whether the default value is explicitly set or not + // First, check if we have a callback + if (attributeData.TryGetNamedArgument("DefaultValueCallback", out TypedConstant defaultValueCallback)) + { + // This must be a valid 'string' value + if (defaultValueCallback is { Type.SpecialType: SpecialType.System_String, Value: string { Length: > 0 } methodName }) + { + ImmutableArray memberSymbols = propertySymbol.ContainingType!.GetMembers(methodName); + + foreach (ISymbol member in memberSymbols) + { + // We need methods which are static and with no parameters (and that is not explicitly implemented) + if (member is not IMethodSymbol { IsStatic: true, Parameters: [], ExplicitInterfaceImplementations: [] } methodSymbol) + { + continue; + } + + // Match the exact method name too + if (methodSymbol.Name != methodName) + { + continue; + } + + bool isNullableValueType = propertySymbol.Type is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + + // We have a candidate, now we need to match the return type. Also handle + // for nullable value types where we're returning the raw value directly. + if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || + (isNullableValueType && SymbolEqualityComparer.Default.Equals(((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0], methodSymbol.ReturnType)) || + (!isNullableValueType && SymbolEqualityComparer.Default.Equals(propertySymbol.Type, methodSymbol.ReturnType))) + { + return new DependencyPropertyDefaultValue.Callback(methodName); + } + + // The method name cannot possibly be valid, we can stop here + break; + } + } + + // Invalid callback, the analyzer will emit an error + return NullInfo; + } + + token.ThrowIfCancellationRequested(); + + // Next, check whether the default value is explicitly set or not if (attributeData.TryGetNamedArgument("DefaultValue", out TypedConstant defaultValue)) { // If the explicit value is anything other than 'null', we can return it directly if (!defaultValue.IsNull) { - return TypedConstantInfo.Create(defaultValue); + return new DependencyPropertyDefaultValue.Constant(TypedConstantInfo.Create(defaultValue)); } // If we do have a default value, we also want to check whether it's the special 'UnsetValue' placeholder. @@ -282,7 +326,7 @@ public static TypedConstantInfo GetDefaultValue( // Last step: we want to validate that the reference is actually to the special placeholder if (fieldSymbol.ContainingType!.HasFullyQualifiedMetadataName(WellKnownTypeNames.GeneratedDependencyProperty)) { - return new TypedConstantInfo.UnsetValue(useWindowsUIXaml); + return new DependencyPropertyDefaultValue.UnsetValue(useWindowsUIXaml); } } } @@ -293,11 +337,13 @@ public static TypedConstantInfo GetDefaultValue( return NullInfo; } + token.ThrowIfCancellationRequested(); + // In all other cases, we'll automatically use the default value of the type in question. // First we need to special case non nullable values, as for those we need 'default'. if (propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }) { - return new TypedConstantInfo.Default(propertySymbol.Type.GetFullyQualifiedName()); + return new DependencyPropertyDefaultValue.Default(propertySymbol.Type.GetFullyQualifiedName()); } // For all other ones, we can just use the 'null' placeholder again @@ -411,8 +457,10 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) string typeMetadata = propertyInfo switch { // Shared codegen - { DefaultValue: TypedConstantInfo.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } - => "null", + { DefaultValue: DependencyPropertyDefaultValue.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + => "null", + { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}{methodName})", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue})", @@ -429,7 +477,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) }, // Codegen for .NET 8 or greater - { DefaultValue: TypedConstantInfo.Null } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) + { DefaultValue: DependencyPropertyDefaultValue.Null } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", { DefaultValue: { } defaultValue } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", @@ -512,7 +560,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) """, isMultiline: true); // If the default value is not what the default field value would be, add an initializer - if (propertyInfo.DefaultValue is not (TypedConstantInfo.Null or TypedConstantInfo.Default)) + if (propertyInfo.DefaultValue is not (DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default or DependencyPropertyDefaultValue.Callback)) { writer.Write($" = {propertyInfo.DefaultValue};"); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index ff70652e7..fb9edc8b3 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -106,7 +106,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) bool isReferenceTypeOrUnconstraindTypeParameter = !propertySymbol.Type.IsValueType; // Also get the default value (this might be slightly expensive, so do it towards the end) - TypedConstantInfo defaultValue = Execute.GetDefaultValue( + DependencyPropertyDefaultValue defaultValue = Execute.GetDefaultValue( context.Attributes[0], propertySymbol, context.SemanticModel, @@ -114,7 +114,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token); // The 'UnsetValue' can only be used when local caching is disabled - if (defaultValue is TypedConstantInfo.UnsetValue && isLocalCachingEnabled) + if (defaultValue is DependencyPropertyDefaultValue.UnsetValue && isLocalCachingEnabled) { return null; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs new file mode 100644 index 000000000..b494f4c5c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs @@ -0,0 +1,77 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Constants; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model representing a default value for a dependency property. +/// +internal abstract partial record DependencyPropertyDefaultValue +{ + /// + /// A type representing a value. + /// + public sealed record Null : DependencyPropertyDefaultValue + { + /// + public override string ToString() + { + return "null"; + } + } + + /// + /// A type representing default value for a specific type. + /// + /// The input type name. + public sealed record Default(string TypeName) : DependencyPropertyDefaultValue + { + /// + public override string ToString() + { + return $"default({TypeName})"; + } + } + + /// + /// A type representing the special unset value. + /// + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. + public sealed record UnsetValue(bool UseWindowsUIXaml) : DependencyPropertyDefaultValue + { + /// + public override string ToString() + { + return $"global::{WellKnownTypeNames.DependencyProperty(UseWindowsUIXaml)}.UnsetValue"; + } + } + + /// + /// A type representing a constant value. + /// + /// The constant value. + public sealed record Constant(TypedConstantInfo Value) : DependencyPropertyDefaultValue + { + /// + public override string ToString() + { + return Value.ToString(); + } + } + + /// + /// A type representing a callback. + /// + /// The name of the callback method to invoke. + public sealed record Callback(string MethodName) : DependencyPropertyDefaultValue + { + /// + public override string ToString() + { + return MethodName; + } + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs index 363ea6516..428d9bf3d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs @@ -34,7 +34,7 @@ internal sealed record DependencyPropertyInfo( Accessibility SetterAccessibility, string TypeName, string TypeNameWithNullabilityAnnotations, - TypedConstantInfo DefaultValue, + DependencyPropertyDefaultValue DefaultValue, bool IsReferenceTypeOrUnconstraindTypeParameter, bool IsLocalCachingEnabled, bool IsPropertyChangedCallbackImplemented, diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index bbc5f5710..48accd4e1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using System.Linq; -using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Helpers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -17,9 +16,20 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// /// A model representing a typed constant item. /// -/// This model is fully serializable and comparable. internal abstract partial record TypedConstantInfo { + /// + /// A type representing a value. + /// + public sealed record Null : TypedConstantInfo + { + /// + public override string ToString() + { + return "null"; + } + } + /// /// A type representing an array. /// @@ -147,42 +157,4 @@ public override string ToString() return $"({TypeName}){valueExpression.NormalizeWhitespace(eol: "\n").ToFullString()}"; } } - - /// - /// A type representing a value. - /// - public sealed record Null : TypedConstantInfo - { - /// - public override string ToString() - { - return "null"; - } - } - - /// - /// A type representing default value for a specific type. - /// - /// The input type name. - public sealed record Default(string TypeName) : TypedConstantInfo - { - /// - public override string ToString() - { - return $"default({TypeName})"; - } - } - - /// - /// A type representing the special unset value. - /// - /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. - public sealed record UnsetValue(bool UseWindowsUIXaml) : TypedConstantInfo - { - /// - public override string ToString() - { - return $"global::{WellKnownTypeNames.DependencyProperty(UseWindowsUIXaml)}.UnsetValue"; - } - } } From c0536991578200b71507e53a2f14108c1f5b7a2b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 6 Dec 2024 21:44:53 -0800 Subject: [PATCH 048/126] Fix value callbacks, add unit tests --- .../DependencyPropertyGenerator.Execute.cs | 19 +- .../Test_DependencyPropertyGenerator.cs | 367 ++++++++++++++++++ 2 files changed, 380 insertions(+), 6 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 6dbbf15ff..e8e3d5d5c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -278,13 +278,20 @@ public static DependencyPropertyDefaultValue GetDefaultValue( continue; } + // We have a candidate, now we need to match the return type. First, + // we just check whether the return is 'object', or an exact match. + if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || + SymbolEqualityComparer.Default.Equals(propertySymbol.Type, methodSymbol.ReturnType)) + { + return new DependencyPropertyDefaultValue.Callback(methodName); + } + bool isNullableValueType = propertySymbol.Type is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; - // We have a candidate, now we need to match the return type. Also handle - // for nullable value types where we're returning the raw value directly. - if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || - (isNullableValueType && SymbolEqualityComparer.Default.Equals(((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0], methodSymbol.ReturnType)) || - (!isNullableValueType && SymbolEqualityComparer.Default.Equals(propertySymbol.Type, methodSymbol.ReturnType))) + // Otherwise, try to see if the return is the type argument of a nullable value type + if (isNullableValueType && + methodSymbol.ReturnType.TypeKind is TypeKind.Struct && + SymbolEqualityComparer.Default.Equals(((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0], methodSymbol.ReturnType)) { return new DependencyPropertyDefaultValue.Callback(methodName); } @@ -460,7 +467,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { DefaultValue: DependencyPropertyDefaultValue.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}{methodName})", + => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}))", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue})", diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 8cdace498..6e6645d77 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -2319,4 +2319,371 @@ public partial string? LastName CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); } + + [TestMethod] + [DataRow("int")] + [DataRow("object")] + [DataRow("object?")] + public void SingleProperty_Int32_WithNoCaching_WithDefaultValueCallback(string returnType) + { + string source = $$""" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValueCallback = nameof(CreateNumber))] + public partial int Number { get; set; } + + private static {{returnType}} CreateNumber() => 42; + } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("int")] + [DataRow("int?")] + [DataRow("object")] + [DataRow("object?")] + public void SingleProperty_NullableOfInt32_WithNoCaching_WithDefaultValueCallback(string returnType) + { + string source = $$""" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValueCallback = nameof(CreateNumber))] + public partial int? Number { get; set; } + + private static {{returnType}} CreateNumber() => 42; + } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int?), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int? Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int? __unboxedValue = (int?)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string")] + [DataRow("string?")] + [DataRow("object")] + [DataRow("object?")] + public void SingleProperty_String_WithNoCaching_WithDefaultValueCallback(string returnType) + { + string source = $$""" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + #nullable enable + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValueCallback = nameof(CreateName))] + public partial string? Name { get; set; } + + private static {{returnType}} CreateName() => "Bob"; + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateName))); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); + } } From b0e178e5dcc72bfd67c2343f9afa473d8ef0ca53 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 6 Dec 2024 22:01:54 -0800 Subject: [PATCH 049/126] Add '[DisallowNull]' to 'DefaultValueCallback' --- .../GeneratedDependencyPropertyAttribute.cs | 3 +++ .../src/GeneratedDependencyPropertyAttribute.cs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs index c94eb6652..bcffcfa02 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs @@ -71,6 +71,9 @@ internal sealed class GeneratedDependencyPropertyAttribute : global::System.Attr /// /// Using this property is mutually exclusive with . /// +#if NET8_0_OR_GREATER + [global::System.Diagnostics.CodeAnalysis.DisallowNull] +#endif public string? DefaultValueCallback { get; init; } = null; /// diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs index 1510b9f33..ffc3dca67 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +#if NET8_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif #if WINDOWS_UWP || HAS_UNO using DependencyObject = Windows.UI.Xaml.DependencyObject; using DependencyProperty = Windows.UI.Xaml.DependencyProperty; @@ -62,6 +65,9 @@ public sealed class GeneratedDependencyPropertyAttribute : Attribute /// /// Using this property is mutually exclusive with . /// +#if NET8_0_OR_GREATER + [DisallowNull] +#endif public string? DefaultValueCallback { get; init; } = null; /// From b4bf0a32aa7c23f3f7c10457fc33a3636e1e2f22 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 10:32:43 -0800 Subject: [PATCH 050/126] Add 'InvalidPropertyDefaultValueCallbackTypeAnalyzer' --- .../AnalyzerReleases.Shipped.md | 3 + .../DependencyPropertyGenerator.Execute.cs | 36 +--- ...ropertyDefaultValueCallbackTypeAnalyzer.cs | 159 ++++++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 44 ++++- 4 files changed, 206 insertions(+), 36 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md index d010b84f3..ce5ace20f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md @@ -19,3 +19,6 @@ WCTDP0009 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera WCTDP0010 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | WCTDP0011 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Warning | WCTDP0012 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0013 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0014 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0015 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index e8e3d5d5c..dc64367b6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -262,42 +262,14 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // This must be a valid 'string' value if (defaultValueCallback is { Type.SpecialType: SpecialType.System_String, Value: string { Length: > 0 } methodName }) { - ImmutableArray memberSymbols = propertySymbol.ContainingType!.GetMembers(methodName); - - foreach (ISymbol member in memberSymbols) + // Check that we can find a potential candidate callback method + if (InvalidPropertyDefaultValueCallbackTypeAnalyzer.TryFindDefaultValueCallbackMethod(propertySymbol, methodName, out IMethodSymbol? methodSymbol)) { - // We need methods which are static and with no parameters (and that is not explicitly implemented) - if (member is not IMethodSymbol { IsStatic: true, Parameters: [], ExplicitInterfaceImplementations: [] } methodSymbol) - { - continue; - } - - // Match the exact method name too - if (methodSymbol.Name != methodName) - { - continue; - } - - // We have a candidate, now we need to match the return type. First, - // we just check whether the return is 'object', or an exact match. - if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || - SymbolEqualityComparer.Default.Equals(propertySymbol.Type, methodSymbol.ReturnType)) + // Validate the method has a valid signature as well + if (InvalidPropertyDefaultValueCallbackTypeAnalyzer.IsDefaultValueCallbackValid(propertySymbol, methodSymbol)) { return new DependencyPropertyDefaultValue.Callback(methodName); } - - bool isNullableValueType = propertySymbol.Type is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; - - // Otherwise, try to see if the return is the type argument of a nullable value type - if (isNullableValueType && - methodSymbol.ReturnType.TypeKind is TypeKind.Struct && - SymbolEqualityComparer.Default.Equals(((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0], methodSymbol.ReturnType)) - { - return new DependencyPropertyDefaultValue.Callback(methodName); - } - - // The method name cannot possibly be valid, we can stop here - break; } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs new file mode 100644 index 000000000..e2577b301 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs @@ -0,0 +1,159 @@ +// 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.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error whenever [GeneratedDependencyProperty] is used with an invalid default value callback argument. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyDefaultValueCallbackTypeAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + InvalidPropertyDeclarationDefaultValueCallbackMixed, + InvalidPropertyDeclarationDefaultValueCallbackNoMethodFound, + InvalidPropertyDeclarationDefaultValueCallbackInvalidMethod + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + context.RegisterSymbolAction(context => + { + // Validate that we do have a property + if (context.Symbol is not IPropertySymbol propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + // If 'DefaultValueCallback' is not set, there's nothing to do + if (!attributeData.TryGetNamedArgument("DefaultValueCallback", out string? defaultValueCallback)) + { + return; + } + + // Emit a diagnostic if 'DefaultValue' is also being set + if (attributeData.TryGetNamedArgument("DefaultValue", out _)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationDefaultValueCallbackMixed, + attributeData.GetLocation(), + propertySymbol)); + } + + // If 'DefaultValueCallback' is 'null', ignore it (Roslyn will already warn here) + if (defaultValueCallback is null) + { + return; + } + + // Emit a diagnostic if we can't find a candidate method + if (!TryFindDefaultValueCallbackMethod(propertySymbol, defaultValueCallback, out IMethodSymbol? methodSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationDefaultValueCallbackNoMethodFound, + attributeData.GetLocation(), + propertySymbol, + defaultValueCallback)); + } + else if (!IsDefaultValueCallbackValid(propertySymbol, methodSymbol)) + { + // Emit a diagnostic if the candidate method is not valid + context.ReportDiagnostic(Diagnostic.Create( + InvalidPropertyDeclarationDefaultValueCallbackInvalidMethod, + attributeData.GetLocation(), + propertySymbol, + defaultValueCallback)); + } + + }, SymbolKind.Property); + }); + } + + /// + /// Tries to find a candidate default value callback method for a given property. + /// + /// The currently being targeted by the analyzer. + /// The name of the default value callback method to look for. + /// The for the resulting default value callback candidate method, if found. + /// Whether could be found. + public static bool TryFindDefaultValueCallbackMethod(IPropertySymbol propertySymbol, string methodName, [NotNullWhen(true)] out IMethodSymbol? methodSymbol) + { + ImmutableArray memberSymbols = propertySymbol.ContainingType!.GetMembers(methodName); + + foreach (ISymbol member in memberSymbols) + { + // We need methods which are static and with no parameters (and that is not explicitly implemented) + if (member is not IMethodSymbol { IsStatic: true, Parameters: [], ExplicitInterfaceImplementations: [] } candidateSymbol) + { + continue; + } + + // Match the exact method name too + if (candidateSymbol.Name == methodName) + { + methodSymbol = candidateSymbol; + + return true; + } + } + + methodSymbol = null; + + return false; + } + + /// + /// Checks whether a given default value callback method is valid for a given property. + /// + /// The currently being targeted by the analyzer. + /// The for the candidate default value callback method to validate. + /// Whether is a valid default value callback method for . + public static bool IsDefaultValueCallbackValid(IPropertySymbol propertySymbol, IMethodSymbol methodSymbol) + { + // We have a candidate, now we need to match the return type. First, + // we just check whether the return is 'object', or an exact match. + if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || + SymbolEqualityComparer.Default.Equals(propertySymbol.Type, methodSymbol.ReturnType)) + { + return true; + } + + bool isNullableValueType = propertySymbol.Type is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + + // Otherwise, try to see if the return is the type argument of a nullable value type + if (isNullableValueType && + methodSymbol.ReturnType.TypeKind is TypeKind.Struct && + SymbolEqualityComparer.Default.Equals(((INamedTypeSymbol)propertySymbol.Type).TypeArguments[0], methodSymbol.ReturnType)) + { + return true; + } + + return false; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 7d3b61cb1..2c04ec645 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -155,10 +155,7 @@ internal static class DiagnosticDescriptors helpLinkUri: "https://aka.ms/toolkit/labs/windows"); /// - /// Gets a for when [ObservableProperty] is used on a property that returns a pointer type. - /// - /// Format: "The property {0}.{1} returns a pointer or function pointer value ([ObservableProperty] must be used on properties of a non pointer-like type)". - /// + /// "The property '{0}' returns a pointer or function pointer value ([ObservableProperty] must be used on properties of a non pointer-like type)". /// public static readonly DiagnosticDescriptor InvalidPropertyDeclarationReturnsPointerType = new( id: "WCTDP0012", @@ -169,4 +166,43 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Properties annotated with [GeneratedDependencyProperty] must not return a pointer value (only reference types and non byref-like types are supported).", helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' is using [GeneratedDependencyProperty] with both 'DefaultValue' and 'DefaultValueCallback' and being set, which is not supported (only one of these properties can be set at a time)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationDefaultValueCallbackMixed = new( + id: "WCTDP0013", + title: "Using [GeneratedDependencyProperty] with both 'DefaultValue' and 'DefaultValueCallback'", + messageFormat: """The property '{0}' is using [GeneratedDependencyProperty] with both 'DefaultValue' and 'DefaultValueCallback' and being set, which is not supported (only one of these properties can be set at a time)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] cannot use both 'DefaultValue' and 'DefaultValueCallback' at the same time.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' is using [GeneratedDependencyProperty] with 'DefaultValueCallback' set to '{1}', but no accessible method with that name was found (make sure the target method is in the same containing type)". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationDefaultValueCallbackNoMethodFound = new( + id: "WCTDP0014", + title: "Using [GeneratedDependencyProperty] with missing 'DefaultValueCallback' method", + messageFormat: """The property '{0}' is using [GeneratedDependencyProperty] with 'DefaultValueCallback' set to '{1}', but no accessible method with that name was found (make sure the target method is in the same containing type)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValueCallback' must use the name of an accessible method in their same containing type.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' is using [GeneratedDependencyProperty] with 'DefaultValueCallback' set to '{1}', but the method has an invalid signature (it must be a static method with no parameters, returning a value compatible with the property type: either the same type, or 'object')". + /// + public static readonly DiagnosticDescriptor InvalidPropertyDeclarationDefaultValueCallbackInvalidMethod = new( + id: "WCTDP0015", + title: "Using [GeneratedDependencyProperty] with invalid 'DefaultValueCallback' method", + messageFormat: """The property '{0}' is using [GeneratedDependencyProperty] with 'DefaultValueCallback' set to '{1}', but the method has an invalid signature (it must be a static method with no parameters, returning a value compatible with the property type: either the same type, or 'object')""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValueCallback' must use the name of a method with a valid signature (it must be a static method with no parameters, returning a value compatible with the property type: either the same type, or 'object').", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); } From ba00de96162ea2ce7cb0fefff2fbec7418176b22 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 12:30:55 -0800 Subject: [PATCH 051/126] Add unit tests for new analyzer --- ...ropertyDefaultValueCallbackTypeAnalyzer.cs | 10 +- .../Test_Analyzers.cs | 231 +++++++++++++++++- 2 files changed, 232 insertions(+), 9 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs index e2577b301..d12d5662c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs @@ -108,8 +108,8 @@ public static bool TryFindDefaultValueCallbackMethod(IPropertySymbol propertySym foreach (ISymbol member in memberSymbols) { - // We need methods which are static and with no parameters (and that is not explicitly implemented) - if (member is not IMethodSymbol { IsStatic: true, Parameters: [], ExplicitInterfaceImplementations: [] } candidateSymbol) + // Ignore all other member types + if (member is not IMethodSymbol candidateSymbol) { continue; } @@ -136,6 +136,12 @@ public static bool TryFindDefaultValueCallbackMethod(IPropertySymbol propertySym /// Whether is a valid default value callback method for . public static bool IsDefaultValueCallbackValid(IPropertySymbol propertySymbol, IMethodSymbol methodSymbol) { + // We need methods which are static and with no parameters (and that are not explicitly implemented) + if (methodSymbol is not { IsStatic: true, Parameters: [], ExplicitInterfaceImplementations: [] }) + { + return false; + } + // We have a candidate, now we need to match the return type. First, // we just check whether the return is 'object', or an exact match. if (methodSymbol.ReturnType.SpecialType is SpecialType.System_Object || diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 3a9c8e330..19657c763 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -659,7 +659,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithMaybeNullAttribute_DoesNotWarn() { - string source = $$""" + const string source = """ using System.Diagnostics.CodeAnalysis; using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -682,7 +682,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_Required_DoesNotWarn() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -703,7 +703,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_NullableDisabled_DoesNotWarn() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -722,7 +722,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithNonNullDefaultValue_DoesNotWarn() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -743,7 +743,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_Warns() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -764,7 +764,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyNonNullableDeclarationAnalyzer_NotNullableType_WithNullDefaultValue_Warns() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -896,7 +896,7 @@ public partial class MyControl : Control [TestMethod] public async Task InvalidPropertyDefaultValueTypeAnalyzer_NullValue_NonNullable_Warns() { - string source = $$""" + const string source = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml.Controls; @@ -938,4 +938,221 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_NoAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_NoDefaultValueCallback1_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_NoDefaultValueCallback2_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = "Bob")] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_NullDefaultValueCallback_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValueCallback = null)] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string", "string")] + [DataRow("string", "string?")] + [DataRow("string", "object")] + [DataRow("string", "object?")] + [DataRow("string?", "string")] + [DataRow("string?", "string?")] + [DataRow("int", "int")] + [DataRow("int", "object")] + [DataRow("int", "object?")] + [DataRow("int?", "int")] + [DataRow("int?", "int?")] + [DataRow("int?", "object")] + [DataRow("int?", "object?")] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_ValidDefaultValueCallback_DoesNotWarn(string propertyType, string returnType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValueCallback = nameof(GetDefaultValue))] + public partial {{propertyType}} {|CS9248:Value|} { get; set; } + + private static {{returnType}} GetDefaultValue() => default!; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_BothDefaultValuePropertiesSet_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0013:GeneratedDependencyProperty(DefaultValue = "Bob", DefaultValueCallback = nameof(GetDefaultName))|}] + public partial string? {|CS9248:Name|} { get; set; } + + private static string? GetDefaultName() => "Bob"; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_MethodNotFound_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0014:GeneratedDependencyProperty(DefaultValueCallback = "MissingMethod")|}] + public partial string? {|CS9248:Name|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_InvalidMethod_ExplicitlyImplemented_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control, IGetDefaultValue + { + [{|WCTDP0014:GeneratedDependencyProperty(DefaultValueCallback = "GetDefaultValue")|}] + public partial string? {|CS9248:Name|} { get; set; } + + static string? IGetDefaultValue.GetDefaultValue() => "Bob"; + } + + public interface IGetDefaultValue + { + static abstract string? GetDefaultValue(); + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("private string? GetDefaultName()")] + [DataRow("private static string? GetDefaultName(int x)")] + [DataRow("private static int GetDefaultName()")] + [DataRow("private static int GetDefaultName(int x)")] + public async Task InvalidPropertyDefaultValueCallbackTypeAnalyzer_InvalidMethod_Warns(string methodSignature) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0015:GeneratedDependencyProperty(DefaultValueCallback = "GetDefaultName")|}] + public partial string? {|CS9248:Name|} { get; set; } + + {{methodSignature}} => default!; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From 8f971c9ea5e75bf8c34a36cbf917bcea70f599bf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 12:40:32 -0800 Subject: [PATCH 052/126] Fix codegen for all default value callback cases --- .../DependencyPropertyGenerator.Execute.cs | 12 ++++++++++-- .../Test_DependencyPropertyGenerator.cs | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index dc64367b6..9555df548 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -439,13 +439,19 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { DefaultValue: DependencyPropertyDefaultValue.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}))", + => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}))", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue})", // Codegen for legacy UWP { IsNet8OrGreater: false } => propertyInfo switch { + { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } + => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", + { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } + => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", + { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: true } + => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } @@ -456,8 +462,10 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) }, // Codegen for .NET 8 or greater - { DefaultValue: DependencyPropertyDefaultValue.Null } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) + { DefaultValue: DependencyPropertyDefaultValue.Null } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName) } + => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", { DefaultValue: { } defaultValue } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 6e6645d77..9dd3b4a05 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -2359,7 +2359,7 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -2481,7 +2481,7 @@ partial class MyControl name: "Number", propertyType: typeof(int?), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -2605,7 +2605,7 @@ partial class MyControl name: "Name", propertyType: typeof(string), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateName))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateName))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] From aa1267082c3079f604bd6decac1238290cfddf03 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 14:06:21 -0800 Subject: [PATCH 053/126] Add 'PropertyDeclarationWithPropertyNameSuffixAnalyzer' --- .../AnalyzerReleases.Shipped.md | 1 + ...dPropertyConflictingDeclarationAnalyzer.cs | 6 +- ...opertyContainingTypeDeclarationAnalyzer.cs | 6 +- ...ropertyDefaultValueCallbackTypeAnalyzer.cs | 6 +- ...dPropertyNonNullableDeclarationAnalyzer.cs | 2 +- ...nvalidPropertySyntaxDeclarationAnalyzer.cs | 6 +- ...clarationWithPropertyNameSuffixAnalyzer.cs | 55 ++++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 13 ++++ .../Test_Analyzers.cs | 65 +++++++++++++++++++ 9 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/PropertyDeclarationWithPropertyNameSuffixAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md index ce5ace20f..34bb94f7d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md @@ -22,3 +22,4 @@ WCTDP0012 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera WCTDP0013 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | WCTDP0014 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | WCTDP0015 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0016 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs index 5bf8bf9ac..06158fb20 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyConflictingDeclarationAnalyzer.cs @@ -42,11 +42,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(context => { - // Validate that we do have a property - if (context.Symbol is not IPropertySymbol propertySymbol) - { - return; - } + IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol; // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs index 10fe9967c..e06c981ef 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyContainingTypeDeclarationAnalyzer.cs @@ -42,11 +42,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(context => { - // Validate that we do have a property - if (context.Symbol is not IPropertySymbol propertySymbol) - { - return; - } + IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol; // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs index d12d5662c..b15719f1f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyDefaultValueCallbackTypeAnalyzer.cs @@ -39,11 +39,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(context => { - // Validate that we do have a property - if (context.Symbol is not IPropertySymbol propertySymbol) - { - return; - } + IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol; // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs index 649ed5373..4d729b2e0 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyNonNullableDeclarationAnalyzer.cs @@ -36,7 +36,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(context => { - // Validate that we do have a property, and that it is of some type that can be explicitly nullable. + // Validate that we have a property that is of some type that can be explicitly nullable. // We're intentionally ignoring 'Nullable' values here, since those are by defintiion nullable. // Additionally, we only care about properties that are explicitly marked as not nullable. // Lastly, we can skip required properties, since for those it's completely fine to be non-nullable. diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs index 29df7b907..b26fa16ad 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertySyntaxDeclarationAnalyzer.cs @@ -35,11 +35,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(context => { - // We're intentionally only looking for properties here - if (context.Symbol is not IPropertySymbol propertySymbol) - { - return; - } + IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol; // If the property isn't using '[GeneratedDependencyProperty]', there's nothing to do if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/PropertyDeclarationWithPropertyNameSuffixAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/PropertyDeclarationWithPropertyNameSuffixAnalyzer.cs new file mode 100644 index 000000000..6052a8beb --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/PropertyDeclarationWithPropertyNameSuffixAnalyzer.cs @@ -0,0 +1,55 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates a diagnostic whenever [GeneratedDependencyProperty] is used on a property with the 'Property' suffix in its name. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class PropertyDeclarationWithPropertyNameSuffixAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = [PropertyDeclarationWithPropertySuffix]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + context.RegisterSymbolAction(context => + { + IPropertySymbol propertySymbol = (IPropertySymbol)context.Symbol; + + // We only want to lookup the attribute if the property name actually ends with the 'Property' suffix + if (!propertySymbol.Name.EndsWith("Property")) + { + return; + } + + // Emit a diagnostic if the property is using '[GeneratedDependencyProperty]' + if (propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + context.ReportDiagnostic(Diagnostic.Create( + PropertyDeclarationWithPropertySuffix, + attributeData.GetLocation(), + propertySymbol)); + } + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 2c04ec645..e254b1a25 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -205,4 +205,17 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Properties annotated with [GeneratedDependencyProperty] and setting 'DefaultValueCallback' must use the name of a method with a valid signature (it must be a static method with no parameters, returning a value compatible with the property type: either the same type, or 'object').", helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' is using [GeneratedDependencyProperty] and has a name ending with the 'Property' suffix, which is redundant (the generated dependency property will always add the 'Property' suffix to the name of its associated property)". + /// + public static readonly DiagnosticDescriptor PropertyDeclarationWithPropertySuffix = new( + id: "WCTDP0016", + title: "Using [GeneratedDependencyProperty] on a property with the 'Property' suffix", + messageFormat: """The property '{0}' is using [GeneratedDependencyProperty] and has a name ending with the 'Property' suffix, which is redundant (the generated dependency property will always add the 'Property' suffix to the name of its associated property)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: "Properties annotated with [GeneratedDependencyProperty] should not have the 'Property' suffix in their name, as it is redundant (the generated dependency properties will always add the 'Property' suffix to the name of their associated properties).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 19657c763..2317a547b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1155,4 +1155,69 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + [DataRow("Name")] + [DataRow("TestProperty")] + public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_NoAttribute_DoesNotWarn(string propertyName) + { + string source = $$""" + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public partial string? {|CS9248:{{propertyName}}|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("Name")] + [DataRow("PropertyGroup")] + public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_ValidName_DoesNotWarn(string propertyName) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:{{propertyName}}|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task PropertyDeclarationWithPropertyNameSuffixAnalyzer_InvalidName_Warns() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [{|WCTDP0016:GeneratedDependencyProperty|}] + public partial string? {|CS9248:TestProperty|} { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From e988aab8922a2e76e9913b50b554c15f08ea638d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 14:07:48 -0800 Subject: [PATCH 054/126] Fix two nullability warnings --- .../EmbeddedResources/GeneratedDependencyPropertyAttribute.cs | 2 +- .../src/GeneratedDependencyPropertyAttribute.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs index bcffcfa02..61577eea5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs @@ -74,7 +74,7 @@ internal sealed class GeneratedDependencyPropertyAttribute : global::System.Attr #if NET8_0_OR_GREATER [global::System.Diagnostics.CodeAnalysis.DisallowNull] #endif - public string? DefaultValueCallback { get; init; } = null; + public string? DefaultValueCallback { get; init; } = null!; /// /// Gets a value indicating whether or not property values should be cached locally, to improve performance. diff --git a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs index ffc3dca67..368fe5fc1 100644 --- a/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/src/GeneratedDependencyPropertyAttribute.cs @@ -68,7 +68,7 @@ public sealed class GeneratedDependencyPropertyAttribute : Attribute #if NET8_0_OR_GREATER [DisallowNull] #endif - public string? DefaultValueCallback { get; init; } = null; + public string? DefaultValueCallback { get; init; } = null!; /// /// Gets a value indicating whether or not property values should be cached locally, to improve performance. From 4a42758ce5296c7f7599cf59cde2fed048757e5c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 7 Dec 2024 14:34:30 -0800 Subject: [PATCH 055/126] Improve formatting for property initialization --- .../DependencyPropertyGenerator.Execute.cs | 66 +++++++++++++++---- .../Test_DependencyPropertyGenerator.cs | 29 +++++--- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 9555df548..3da03d7d2 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -439,7 +439,10 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { DefaultValue: DependencyPropertyDefaultValue.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } - => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}))", + => $""" + global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create( + createDefaultValueCallback: new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName})) + """, { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue})", @@ -447,27 +450,63 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { IsNet8OrGreater: false } => propertyInfo switch { { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } - => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", + => $""" + global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create( + createDefaultValueCallback: new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), + propertyChangedCallback: static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e)) + """, { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } - => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", + => $""" + global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create( + createDefaultValueCallback: new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), + propertyChangedCallback: static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e)) + """, { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: true } - => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", + => $$""" + global::{{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}}.Create( + createDefaultValueCallback: new {{WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}}({{methodName}}), + propertyChangedCallback: static (d, e) => { (({{typeQualifiedName}})d).On{{propertyInfo.PropertyName}}PropertyChanged(e); (({{typeQualifiedName}})d).OnPropertyChanged(e); }) + """, { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: false } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e))", + => $""" + new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}( + defaultValue: {defaultValue}, + propertyChangedCallback: static (d, e) => (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e)) + """, { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: true } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e))", + => $""" + new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}( + defaultValue: {defaultValue}, + propertyChangedCallback: static (d, e) => (({typeQualifiedName})d).OnPropertyChanged(e)) + """, { DefaultValue: { } defaultValue, IsPropertyChangedCallbackImplemented: true, IsSharedPropertyChangedCallbackImplemented: true } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, static (d, e) => {{ (({typeQualifiedName})d).On{propertyInfo.PropertyName}PropertyChanged(e); (({typeQualifiedName})d).OnPropertyChanged(e); }})", + => $$""" + new global::{{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}}( + defaultValue: {{defaultValue}}, + propertyChangedCallback: static (d, e) => { (({{typeQualifiedName}})d).On{{propertyInfo.PropertyName}}PropertyChanged(e); (({{typeQualifiedName}})d).OnPropertyChanged(e); }) + """, _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), }, // Codegen for .NET 8 or greater { DefaultValue: DependencyPropertyDefaultValue.Null } - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}(null, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + => $""" + new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}( + defaultValue: null, + propertyChangedCallback: global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}()) + """, { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName) } - => $"global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create(new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + => $""" + global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}.Create( + createDefaultValueCallback: new {WellKnownTypeNames.CreateDefaultValueCallback(propertyInfo.UseWindowsUIXaml)}({methodName}), + propertyChangedCallback: global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}()) + """, { DefaultValue: { } defaultValue } and ({ IsPropertyChangedCallbackImplemented: true } or { IsSharedPropertyChangedCallbackImplemented: true }) - => $"new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}({defaultValue}, global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}())", + => $""" + new global::{WellKnownTypeNames.PropertyMetadata(propertyInfo.UseWindowsUIXaml)}( + defaultValue: {defaultValue}, + propertyChangedCallback: global::{GeneratorName}.PropertyChangedCallbacks.{propertyInfo.PropertyName}()) + """, _ => throw new ArgumentException($"Invalid default value '{propertyInfo.DefaultValue}'."), }; @@ -477,13 +516,16 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) /// """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); - writer.WriteLine($$""" + writer.Write($$""" public static readonly global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}} {{propertyInfo.PropertyName}}Property = global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}}.Register( name: "{{propertyInfo.PropertyName}}", propertyType: typeof({{propertyInfo.TypeName}}), ownerType: typeof({{typeQualifiedName}}), - typeMetadata: {{typeMetadata}}); + typeMetadata: """, isMultiline: true); + writer.IncreaseIndent(); + writer.WriteLine($"{typeMetadata});", isMultiline: true); + writer.DecreaseIndent(); writer.WriteLine(); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 9dd3b4a05..cfe303d5c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -175,7 +175,9 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: default(int), + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -698,7 +700,9 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: default(int), + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -969,7 +973,9 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(42, global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: 42, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -1124,7 +1130,9 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: default(int), + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -1283,7 +1291,9 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int), global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: default(int), + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.Number())); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -2359,7 +2369,8 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create( + createDefaultValueCallback: new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -2481,7 +2492,8 @@ partial class MyControl name: "Number", propertyType: typeof(int?), ownerType: typeof(MyControl), - typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create( + createDefaultValueCallback: new Windows.UI.Xaml.CreateDefaultValueCallback(CreateNumber))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -2605,7 +2617,8 @@ partial class MyControl name: "Name", propertyType: typeof(string), ownerType: typeof(MyControl), - typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create(new Windows.UI.Xaml.CreateDefaultValueCallback(CreateName))); + typeMetadata: global::Windows.UI.Xaml.PropertyMetadata.Create( + createDefaultValueCallback: new Windows.UI.Xaml.CreateDefaultValueCallback(CreateName))); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] From d40bee5fb11498f3e8991318b5db4976943efc73 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 8 Dec 2024 19:58:33 -0800 Subject: [PATCH 056/126] Bug fixes to embedded mode --- .../GeneratedDependencyProperty.cs | 2 +- .../GeneratedDependencyPropertyAttribute.cs | 2 +- ...t.WinUI.DependencyPropertyGenerator.targets | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs index 5364e9c10..e35abebf6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyProperty.cs @@ -6,7 +6,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if GENERATED_DEPENDENCY_PROPERTY_PRIVATE_ASSETS_ALL_MODE +#if GENERATED_DEPENDENCY_PROPERTY_EMBEDDED_MODE namespace CommunityToolkit.WinUI { diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs index 61577eea5..0e7c0fafe 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/EmbeddedResources/GeneratedDependencyPropertyAttribute.cs @@ -6,7 +6,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#if GENERATED_DEPENDENCY_PROPERTY_PRIVATE_ASSETS_ALL_MODE +#if GENERATED_DEPENDENCY_PROPERTY_ATTRIBUTE_EMBEDDED_MODE namespace CommunityToolkit.WinUI { diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index 7806f34d3..933629788 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -7,21 +7,29 @@ false + + + false + false + + - - - $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML + + + $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_USE_WINDOWS_UI_XAML + $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_ATTRIBUTE_EMBEDDED_MODE + $(DefineConstants);GENERATED_DEPENDENCY_PROPERTY_EMBEDDED_MODE + Condition="'$(EnableGeneratedDependencyPropertyAttributeEmbeddedMode)' == 'true' OR '$(EnableGeneratedDependencyPropertyEmbeddedMode)' == 'true'"> @@ -36,7 +44,7 @@ + Text="This project is referencing the '[GeneratedDependencyProperty]' .dll file, but it's also enabling the embedded mode for its public APIs. The embedded mode can only be used when the .dll file is not being referenced. Make sure to use 'PrivateAssets="all"' and 'ExcludeAssets="lib"' in the '<PackageReference>' element for the NuGet package." /> From b48e37ff4e6395df3f63d4f50dad1f2bc915786a Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 9 Dec 2024 11:17:48 -0600 Subject: [PATCH 057/126] Update components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs Co-authored-by: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> --- .../Extensions/ITypeSymbolExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index df8431653..eba208177 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -106,7 +106,7 @@ static void BuildFrom(ISymbol? symbol, ref readonly ImmutableArrayBuilder builder.AddRange(symbol.MetadataName.AsSpan()); break; - // Other namespaces (ie. the one right before global) skip the leading '.' + // Other namespaces (i.e. the one right before global) skip the leading '.' case INamespaceSymbol { IsGlobalNamespace: false }: builder.AddRange(symbol.MetadataName.AsSpan()); break; From a943c7b418cc93805f772361c7e2cf340e1a285a Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 9 Dec 2024 11:17:56 -0600 Subject: [PATCH 058/126] Update components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs Co-authored-by: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> --- .../Helpers/CSharpGeneratorTest{TGenerator}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs index 03d746354..5390d1858 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -211,7 +211,7 @@ private static void RunGenerator( { Compilation originalCompilation = CreateCompilation(source, languageVersion); - // Create the generator driver with the D2D shader generator + // Create the generator driver with the specified generator GeneratorDriver driver = CSharpGeneratorDriver.Create(new TGenerator()).WithUpdatedParseOptions(originalCompilation.SyntaxTrees.First().Options); // Run all source generators on the input source code From 42d3211e414abad1d11511754c1355f0eff92236 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Dec 2024 09:59:26 -0800 Subject: [PATCH 059/126] Fix typos --- .../Extensions/CompilationExtensions.cs | 2 +- .../Helpers/IndentedTextWriter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs index ff06af81c..f3c256381 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/CompilationExtensions.cs @@ -34,7 +34,7 @@ public static bool IsLanguageVersionPreview(this Compilation compilation) } /// - /// Gets whether the current target is a WinRT application (ie. legacy UWP). + /// Gets whether the current target is a WinRT application (i.e. legacy UWP). /// /// The input instance to inspect. /// Whether the current target is a WinRT application. diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs index 6d88ecb97..0883e84d1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs @@ -97,7 +97,7 @@ public void IncreaseIndent() } // Set both the current indentation and the current position in the indentations - // array to the expected indentation for the incremented level (ie. one level more). + // array to the expected indentation for the incremented level (i.e. one level more). this.currentIndentation = this.availableIndentations[this.currentIndentationLevel] ??= this.availableIndentations[this.currentIndentationLevel - 1] + DefaultIndentation; } From e481f480cc68bd29cbce561983582a5f37b2157b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 18:12:05 -0800 Subject: [PATCH 060/126] Add draft 'UseGeneratedDependencyPropertyOnManualPropertyAnalyzer' --- .../AnalyzerReleases.Shipped.md | 1 + ...endencyPropertyOnManualPropertyAnalyzer.cs | 525 ++++++++++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 18 + .../Test_Analyzers.cs | 203 +++++++ 4 files changed, 747 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md index 34bb94f7d..e547e0ee3 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md @@ -23,3 +23,4 @@ WCTDP0013 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera WCTDP0014 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | WCTDP0015 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | WCTDP0016 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info | +WCTDP0017 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs new file mode 100644 index 000000000..316edea2f --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -0,0 +1,525 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates a suggestion whenever [GeneratedDependencytProperty] should be used over a manual property. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UseGeneratedDependencyPropertyOnManualPropertyAnalyzer : DiagnosticAnalyzer +{ + /// + /// The number of pooled flags per stack (ie. how many properties we expect on average per type). + /// + private const int NumberOfPooledFlagsPerStack = 20; + + /// + /// Shared pool for instances for properties. + /// + [SuppressMessage("MicrosoftCodeAnalysisPerformance", "RS1008", Justification = "This is a pool of (empty) dictionaries, it is not actually storing compilation data.")] + private static readonly ObjectPool> PropertyMapPool = new(static () => new Dictionary(SymbolEqualityComparer.Default)); + + /// + /// Shared pool for instances for fields, for dependency properties. + /// + [SuppressMessage("MicrosoftCodeAnalysisPerformance", "RS1008", Justification = "This is a pool of (empty) dictionaries, it is not actually storing compilation data.")] + private static readonly ObjectPool> FieldMapPool = new(static () => new Dictionary(SymbolEqualityComparer.Default)); + + /// + /// Shared pool for -s of property flags, one per type being processed. + /// + private static readonly ObjectPool> PropertyFlagsStackPool = new(CreateFlagsStack); + + /// + /// Shared pool for -s of field flags, one per type being processed. + /// + private static readonly ObjectPool> FieldFlagsStackPool = new(CreateFlagsStack); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = [UseGeneratedDependencyPropertyForManualProperty]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the XAML mode to use + bool useWindowsUIXaml = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml); + + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + // Get the 'DependencyObject' symbol + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject(useWindowsUIXaml)) is not { } dependencyObjectSymbol) + { + return; + } + + // Get the symbol for the 'GetValue' method as well + if (dependencyObjectSymbol.GetMembers("GetValue") is not [IMethodSymbol { Parameters: [_], ReturnType.SpecialType: SpecialType.System_Object } getValueSymbol]) + { + return; + } + + // Get the symbol for the 'SetValue' method as well + if (dependencyObjectSymbol.GetMembers("SetValue") is not [IMethodSymbol { Parameters: [_, _], ReturnsVoid: true } setValueSymbol]) + { + return; + } + + // We also need the 'DependencyProperty' and 'PropertyMetadata' symbols + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyProperty(useWindowsUIXaml)) is not { } dependencyPropertySymbol || + context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.PropertyMetadata(useWindowsUIXaml)) is not { } propertyMetadataSymbol) + { + return; + } + + // Next, we need to get the 'DependencyProperty.Register' symbol as well, to validate initializers. + // No need to validate this more, as there's only a single overload defined on this type. + if (dependencyPropertySymbol.GetMembers("Register") is not [IMethodSymbol dependencyPropertyRegisterSymbol]) + { + return; + } + + context.RegisterSymbolStartAction(context => + { + // We only care about types that could derive from 'DependencyObject' + if (context.Symbol is not INamedTypeSymbol { IsStatic: false, IsReferenceType: true, BaseType.SpecialType: not SpecialType.System_Object } typeSymbol) + { + return; + } + + // If the type does not derive from 'DependencyObject', ignore it + if (!typeSymbol.InheritsFromType(dependencyObjectSymbol)) + { + return; + } + + Dictionary propertyMap = PropertyMapPool.Allocate(); + Dictionary fieldMap = FieldMapPool.Allocate(); + Stack propertyFlagsStack = PropertyFlagsStackPool.Allocate(); + Stack fieldFlagsStack = FieldFlagsStackPool.Allocate(); + + // Crawl all members to discover properties that might be of interest + foreach (ISymbol memberSymbol in typeSymbol.GetMembers()) + { + // First, look for properties that might be valid candidates for conversion + if (memberSymbol is IPropertySymbol + { + IsStatic: false, + IsPartialDefinition: false, + PartialDefinitionPart: null, + PartialImplementationPart: null, + ReturnsByRef: false, + ReturnsByRefReadonly: false, + Type.IsRefLikeType: false, + GetMethod: not null, + SetMethod.IsInitOnly: false + } propertySymbol) + { + // We can safely ignore properties that already have '[GeneratedDependencyProperty]' + if (propertySymbol.HasAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols)) + { + continue; + } + + // Take a new flags object from the stack or create a new one otherwise + PropertyFlags flags = propertyFlagsStack.Count > 0 + ? propertyFlagsStack.Pop() + : new(); + + // Track the property for later + propertyMap.Add(propertySymbol, flags); + } + else if (memberSymbol is IFieldSymbol + { + DeclaredAccessibility: Accessibility.Public, + IsStatic: true, + IsReadOnly: true, + IsFixedSizeBuffer: false, + IsRequired: false, + Type.IsReferenceType: true, + IsVolatile: false + } fieldSymbol) + { + // We only care about fields that are 'DependencyProperty' + if (!SymbolEqualityComparer.Default.Equals(dependencyPropertySymbol, fieldSymbol.Type)) + { + continue; + } + + // Same as above for the field flags + fieldMap.Add( + key: fieldSymbol, + value: fieldFlagsStack.Count > 0 ? fieldFlagsStack.Pop() : new FieldFlags()); + } + } + + // We want to process both accessors, where we specifically need both the syntax + // and their semantic model to verify what they're doing. We can use a code callback. + context.RegisterOperationBlockAction(context => + { + // Handle a 'get' accessor (for any property) + void HandleGetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFlags) + { + // We expect a top-level block operation, that immediately returns an expression + if (context.OperationBlocks is not [IBlockOperation { Operations: [IReturnOperation returnOperation] }]) + { + return; + } + + // Next, check whether we have an invocation operation. This is the case when the getter is just + // calling 'GetValue' and returning it directly, which only works when the property type allows + // direct conversion. Generally speaking this happens when properties are just of type 'object'. + if (returnOperation is not { ReturnedValue: IInvocationOperation invocationOperation }) + { + // Alternatively, we expect a conversion (a cast) + if (returnOperation is not { ReturnedValue: IConversionOperation { IsTryCast: false } conversionOperation }) + { + return; + } + + // Check the conversion is actually valid + if (!SymbolEqualityComparer.Default.Equals(propertySymbol.Type, conversionOperation.Type)) + { + return; + } + + // Try to extract the invocation from the conversion + if (conversionOperation.Operand is not IInvocationOperation operandInvocationOperation) + { + return; + } + + invocationOperation = operandInvocationOperation; + } + + // Now that we have the invocation, first filter the target method + if (invocationOperation.TargetMethod is not { Name: "GetValue", IsGenericMethod: false, IsStatic: false } methodSymbol) + { + return; + } + + // Next, make sure we're actually calling 'DependencyObject.GetValue' + if (!SymbolEqualityComparer.Default.Equals(methodSymbol, getValueSymbol)) + { + return; + } + + // Make sure we have one argument, which will be the dependency property + if (invocationOperation.Arguments is not [{ } dependencyPropertyArgument]) + { + return; + } + + // This argument should be a field reference (we'll fully validate it later) + if (dependencyPropertyArgument.Value is not IFieldReferenceOperation { Field: { } fieldSymbol }) + { + return; + } + + // The field must follow the expected naming pattern. We can check this just here in the getter. + // If this is valid, the whole property will be skipped anyway, so no need to do it twice. + if (fieldSymbol.Name != $"{propertySymbol.Name}Property") + { + return; + } + + // We can in the meantime at least verify that we do have the field in the set + if (!fieldMap.ContainsKey(fieldSymbol)) + { + return; + } + + // If the property is also valid, then the accessor is valid + propertyFlags.GetValueTargetField = fieldSymbol; + } + + // Handle a 'set' accessor (for any property) + void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFlags) + { + // We expect a top-level block operation, that immediately performs an invocation + if (context.OperationBlocks is not [IBlockOperation { Operations: [IExpressionStatementOperation { Operation: IInvocationOperation invocationOperation }] }]) + { + return; + } + + // Brief filtering of the target method + if (invocationOperation.TargetMethod is not { Name: "SetValue", IsGenericMethod: false, IsStatic: false } methodSymbol) + { + return; + } + + // First, check that we're calling 'DependencyObject.SetValue' + if (!SymbolEqualityComparer.Default.Equals(methodSymbol, setValueSymbol)) + { + return; + } + + // We matched the method, now let's validate the arguments + if (invocationOperation.Arguments is not [{ } dependencyPropertyArgument, { } valueArgument]) + { + return; + } + + // Like for the getter, the first argument should be a field reference... + if (dependencyPropertyArgument.Value is not IFieldReferenceOperation { Field: { } fieldSymbol }) + { + return; + } + + // ...and the field should be in the set (not fully guaranteed to be valid yet, but partially) + if (!fieldMap.ContainsKey(fieldSymbol)) + { + return; + } + + // The value is just the 'value' keyword + if (valueArgument.Value is not IParameterReferenceOperation { Syntax: IdentifierNameSyntax { Identifier.Text: "value" } }) + { + // If this is not the case, check whether the parameter reference was wrapped in a conversion (boxing) + if (valueArgument.Value is not IConversionOperation { IsTryCast: false, Type.SpecialType: SpecialType.System_Object } conversionOperation) + { + return; + } + + // Check for 'value' again + if (conversionOperation.Operand is not IParameterReferenceOperation { Syntax: IdentifierNameSyntax { Identifier.Text: "value" } }) + { + return; + } + } + + // The 'set' accessor is valid if the field is valid, like above + propertyFlags.SetValueTargetField = fieldSymbol; + } + + // Only look for method symbols, for property accessors + if (context.OwningSymbol is not IMethodSymbol { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet, AssociatedSymbol: IPropertySymbol propertySymbol }) + { + return; + } + + // If so, check that we are actually processing one of the properties we care about + if (!propertyMap.TryGetValue(propertySymbol, out PropertyFlags? propertyFlags)) + { + return; + } + + // Handle the 'get' and 'set' logic + if (SymbolEqualityComparer.Default.Equals(propertySymbol.GetMethod, context.OwningSymbol)) + { + HandleGetAccessor(propertySymbol, propertyFlags); + } + else if (SymbolEqualityComparer.Default.Equals(propertySymbol.SetMethod, context.OwningSymbol)) + { + HandleSetAccessor(propertySymbol, propertyFlags); + } + }); + + // Same as above, but targeting field initializers (we can't just inspect field symbols) + context.RegisterOperationAction(context => + { + // Only look for field symbols, which we should always get here, and an invocation in the initializer block (the 'DependencyProperty.Register' call) + if (context.Operation is not IFieldInitializerOperation { InitializedFields: [{ } fieldSymbol], Value: IInvocationOperation invocationOperation }) + { + return; + } + + // Check that the field is one of the ones we expect to encounter + if (!fieldMap.TryGetValue(fieldSymbol, out FieldFlags? fieldFlags)) + { + return; + } + + // Validate that we are calling 'DependencyProperty.Register' + if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod, dependencyPropertyRegisterSymbol)) + { + return; + } + + // Next, make sure we have the arguments we expect + if (invocationOperation.Arguments is not [{ } nameArgument, { } propertyTypeArgument, { } ownerTypeArgument, { } propertyMetadataArgument]) + { + return; + } + + // We cannot validate the property name from here yet, but let's check it's a constant, and save it for later + if (nameArgument.Value.ConstantValue is not { HasValue: true, Value: string propertyName }) + { + return; + } + + // Extract the property type, we can validate it later + if (propertyTypeArgument.Value is not ITypeOfOperation { TypeOperand: { } propertyTypeSymbol }) + { + return; + } + + // Extract the owning type, we can validate it right now + if (ownerTypeArgument.Value is not ITypeOfOperation { TypeOperand: { } owningTypeSymbol }) + { + return; + } + + // The owning type always has to be exactly the same as the containing type + if (!SymbolEqualityComparer.Default.Equals(owningTypeSymbol, typeSymbol)) + { + return; + } + + // For now, just check that the metadata is 'null' + if (propertyMetadataArgument.Value.ConstantValue is not { HasValue: true, Value: null }) + { + return; + } + + fieldFlags.PropertyName = propertyName; + fieldFlags.PropertyType = propertyTypeSymbol; + fieldFlags.IsInitializerValid = true; + }, OperationKind.FieldInitializer); + + // Finally, we can consume this information when we finish processing the symbol + context.RegisterSymbolEndAction(context => + { + // Emit a diagnostic for each property that was a valid match + foreach (KeyValuePair pair in propertyMap) + { + // Make sure we have target fields for each accessor. This also implies the accessors themselves are valid. + if (pair.Value is not { GetValueTargetField: { } getValueFieldSymbol, SetValueTargetField: { } setValueFieldSymbol }) + { + continue; + } + + // The two fields must of course be the same + if (!SymbolEqualityComparer.Default.Equals(getValueFieldSymbol, setValueFieldSymbol)) + { + continue; + } + + // Next, check that the field are present in the mapping (otherwise for sure they're not valid) + if (!fieldMap.TryGetValue(getValueFieldSymbol, out FieldFlags? fieldFlags)) + { + continue; + } + + // Validate the combination of accessors and target field, and warn if that's the case + if (fieldFlags.PropertyName == pair.Key.Name && + SymbolEqualityComparer.Default.Equals(fieldFlags.PropertyType, pair.Key.Type) && + fieldFlags.IsInitializerValid) + { + context.ReportDiagnostic(Diagnostic.Create( + UseGeneratedDependencyPropertyForManualProperty, + pair.Key.Locations.FirstOrDefault(), + pair.Key)); + } + } + + // Before clearing the dictionary, move back all values to the stack + foreach (PropertyFlags propertyFlags in propertyMap.Values) + { + // Make sure the flags is cleared before returning it + propertyFlags.GetValueTargetField = null; + propertyFlags.SetValueTargetField = null; + + propertyFlagsStack.Push(propertyFlags); + } + + // Same for the field flags + foreach (FieldFlags fieldFlags in fieldMap.Values) + { + fieldFlags.PropertyName = null; + fieldFlags.PropertyType = null; + fieldFlags.IsInitializerValid = false; + + fieldFlagsStack.Push(fieldFlags); + } + + // We are now done processing the symbol, we can return the dictionary. + // Note that we must clear it before doing so to avoid leaks and issues. + propertyMap.Clear(); + + PropertyMapPool.Free(propertyMap); + + // Also do the same for the stacks, except we don't need to clean them (since it roots no compilation objects) + PropertyFlagsStackPool.Free(propertyFlagsStack); + FieldFlagsStackPool.Free(fieldFlagsStack); + }); + }, SymbolKind.NamedType); + }); + } + + /// + /// Produces a new instance to pool. + /// + /// The type of flags objects to create. + /// The resulting instance to use. + private static Stack CreateFlagsStack() + where T : class, new() + { + static IEnumerable EnumerateFlags() + { + for (int i = 0; i < NumberOfPooledFlagsPerStack; i++) + { + yield return new(); + } + } + + return new(EnumerateFlags()); + } + + /// + /// Flags to track properties to warn on. + /// + private sealed class PropertyFlags + { + /// + /// The target field for the GetValue method. + /// + public IFieldSymbol? GetValueTargetField; + + /// + /// The target field for the SetValue method. + /// + public IFieldSymbol? SetValueTargetField; + } + + /// + /// Flags to track fields. + /// + private sealed class FieldFlags + { + /// + /// The name of the property. + /// + public string? PropertyName; + + /// + /// The type of the property (as in, of values that can be assigned to it). + /// + public ITypeSymbol? PropertyType; + + /// + /// Whether the initializer is valid. + /// + public bool IsInitializerValid; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index e254b1a25..61138c936 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -11,6 +11,11 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Diagnostics; /// internal static class DiagnosticDescriptors { + /// + /// The diagnostic id for . + /// + public const string UseGeneratedDependencyPropertyForManualPropertyId = "WCTDP0017"; + /// /// "The property '{0}' cannot be used to generate a dependency property, as its declaration is not valid (it must be an instance (non static) partial property, with a getter and a setter that is not init-only)". /// @@ -218,4 +223,17 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Properties annotated with [GeneratedDependencyProperty] should not have the 'Property' suffix in their name, as it is redundant (the generated dependency properties will always add the 'Property' suffix to the name of their associated properties).", helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The manual property '{0}' can be converted to a partial property using [GeneratedDependencyProperty], which is recommended (doing so makes the code less verbose and results in more optimized code)". + /// + public static readonly DiagnosticDescriptor UseGeneratedDependencyPropertyForManualProperty = new( + id: UseGeneratedDependencyPropertyForManualPropertyId, + title: "Prefer using [GeneratedDependencyProperty] over manual properties", + messageFormat: """The manual property '{0}' can be converted to a partial property using [GeneratedDependencyProperty], which is recommended (doing so makes the code less verbose and results in more optimized code)""", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: "Manual properties should be converted to partial properties using [GeneratedDependencyProperty] when possible, which is recommended (doing so makes the code less verbose and results in more optimized code).", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 2317a547b..726bacd3f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1220,4 +1220,207 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_FieldNotInitialized_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty; + + public string? Name + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_FieldWithDifferentName_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty OtherNameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name + { + get => (string?)GetValue(OtherNameProperty); + set => SetValue(OtherNameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("null", "typeof(string)", "typeof(MyControl)", "null")] + [DataRow("\"NameProperty\"", "typeof(string)", "typeof(MyControl)", "null")] + [DataRow("\"OtherName\"", "typeof(string)", "typeof(MyControl)", "null")] + [DataRow("\"Name\"", "typeof(int)", "typeof(MyControl)", "null")] + [DataRow("\"Name\"", "typeof(MyControl)", "typeof(MyControl)", "null")] + [DataRow("\"Name\"", "typeof(object)", "typeof(MyControl)", "null")] + [DataRow("\"Name\"", "typeof(string)", "typeof(string)", "null")] + [DataRow("\"Name\"", "typeof(string)", "typeof(Control)", "null")] + [DataRow("\"Name\"", "typeof(string)", "typeof(DependencyObject)", "null")] + [DataRow("\"Name\"", "typeof(string)", "typeof(MyControl)", "new PropertyMetadata(42)")] + [DataRow("\"Name\"", "typeof(string)", "typeof(MyControl)", "new PropertyMetadata(null, (d, e) => { })")] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_InvalidRegisterArguments_DoesNotWarn( + string name, + string propertyType, + string ownerType, + string typeMetadata) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: {{name}}, + propertyType: {{propertyType}}, + ownerType: {{ownerType}}, + typeMetadata: {{typeMetadata}}); + + public string? Name + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_MissingGetter_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name + { + set => SetValue(NameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_MissingSetter_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name + { + get => (string?)GetValue(NameProperty); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + [DataRow("string", "string")] + [DataRow("string", "string?")] + [DataRow("object", "object")] + [DataRow("object", "object?")] + [DataRow("int", "int")] + [DataRow("int?", "int?")] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_Warns( + string dependencyPropertyType, + string propertyType) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: null); + + public {{propertyType}} {|WCTDP0017:Name|} + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From ef70aab7dd703e1c5ea192fa0627cda954f9299e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:25:06 -0800 Subject: [PATCH 061/126] Pass the location of the target field --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 316edea2f..3a6dbe99e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -392,9 +392,15 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla return; } + // Find the parent field for the operation (we're guaranteed to only fine one) + if (context.Operation.Syntax.FirstAncestor()?.GetLocation() is not Location fieldLocation) + { + return; + } + fieldFlags.PropertyName = propertyName; fieldFlags.PropertyType = propertyTypeSymbol; - fieldFlags.IsInitializerValid = true; + fieldFlags.FieldLocation = fieldLocation; }, OperationKind.FieldInitializer); // Finally, we can consume this information when we finish processing the symbol @@ -421,14 +427,27 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla continue; } - // Validate the combination of accessors and target field, and warn if that's the case - if (fieldFlags.PropertyName == pair.Key.Name && - SymbolEqualityComparer.Default.Equals(fieldFlags.PropertyType, pair.Key.Type) && - fieldFlags.IsInitializerValid) + // We only support rewriting when the property name matches the field being initialized. + // Note that the property name here is the literal being passed for the 'name' parameter. + if (fieldFlags.PropertyName != pair.Key.Name) + { + continue; + } + + // Make sure that the 'propertyType' value matches the actual type of the property. + // We are intentionally not handling combinations of nullable value types here. + if (!SymbolEqualityComparer.Default.Equals(fieldFlags.PropertyType, pair.Key.Type)) + { + return; + } + + // Finally, check whether the field was valid (if so, we will have a valid location) + if (fieldFlags.FieldLocation is Location fieldLocation) { context.ReportDiagnostic(Diagnostic.Create( UseGeneratedDependencyPropertyForManualProperty, pair.Key.Locations.FirstOrDefault(), + [fieldLocation], pair.Key)); } } @@ -448,7 +467,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla { fieldFlags.PropertyName = null; fieldFlags.PropertyType = null; - fieldFlags.IsInitializerValid = false; + fieldFlags.FieldLocation = null; fieldFlagsStack.Push(fieldFlags); } @@ -518,8 +537,8 @@ private sealed class FieldFlags public ITypeSymbol? PropertyType; /// - /// Whether the initializer is valid. + /// The location of the target field being initialized. /// - public bool IsInitializerValid; + public Location? FieldLocation; } } From 08707e6a08b00a6d6aef143393cbb2a417991c32 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:30:27 -0800 Subject: [PATCH 062/126] Add empty 'CodeFixers' project --- ...endencyPropertyGenerator.CodeFixers.csproj | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/CommunityToolkit.DependencyPropertyGenerator.CodeFixers.csproj diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/CommunityToolkit.DependencyPropertyGenerator.CodeFixers.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/CommunityToolkit.DependencyPropertyGenerator.CodeFixers.csproj new file mode 100644 index 000000000..76b6c0f7c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/CommunityToolkit.DependencyPropertyGenerator.CodeFixers.csproj @@ -0,0 +1,20 @@ + + + netstandard2.0 + enable + true + true + + + $(NoWarn);IDE0130 + + + + + + + + + + + From 2389d335130fc1839e22fe54130f537e93157c0c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:30:36 -0800 Subject: [PATCH 063/126] Bump 'Microsoft.CodeAnalysis.CSharp' to latest --- ...yToolkit.DependencyPropertyGenerator.SourceGenerators.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 6b470e010..76d0c208f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -10,7 +10,7 @@ - + From 0281e3662916fa96b17093e0fcb7c936be52f876 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:34:47 -0800 Subject: [PATCH 064/126] Pack code fixers into NuGet package --- ...ommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index 18a5c1315..9f3c18d45 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -25,9 +25,10 @@ - + + @@ -39,7 +40,8 @@ - + + From a032d85220d1793f46c2498dbc260c61a503bc53 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:34:57 -0800 Subject: [PATCH 065/126] Add 'InternalsVisibleTo' for code fixers --- ...olkit.DependencyPropertyGenerator.SourceGenerators.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj index 76d0c208f..994ec7f6f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj @@ -9,6 +9,11 @@ $(NoWarn);IDE0130 + + + + + From 85240215b1f94a7123b5d0e9566c72757bf3a90d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:49:42 -0800 Subject: [PATCH 066/126] Add draft 'UseGeneratedDependencyPropertyOnManualPropertyCodeFixer' --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs new file mode 100644 index 000000000..3b1ca0133 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -0,0 +1,277 @@ +// 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.Collections.Immutable; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.Mvvm.CodeFixers; + +/// +/// A code fixer that converts manual properties into partial properties using [GeneratedDependencytProperty]. +/// +[ExportCodeFixProvider(LanguageNames.CSharp)] +[Shared] +public sealed class UseGeneratedDependencyPropertyOnManualPropertyCodeFixer : CodeFixProvider +{ + /// + public override ImmutableArray FixableDiagnosticIds { get; } = [UseGeneratedDependencyPropertyForManualPropertyId]; + + /// + public override Microsoft.CodeAnalysis.CodeFixes.FixAllProvider? GetFixAllProvider() + { + return new FixAllProvider(); + } + + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Diagnostic diagnostic = context.Diagnostics[0]; + TextSpan diagnosticSpan = context.Span; + + // We can only possibly fix diagnostics with an additional location + if (diagnostic.AdditionalLocations is not [{ } fieldLocation]) + { + return; + } + + // This code fixer needs the semantic model, so check that first + if (!context.Document.SupportsSemanticModel) + { + return; + } + + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // Get the property declaration and the field declaration from the target diagnostic + if (root!.FindNode(diagnosticSpan) is PropertyDeclarationSyntax propertyDeclaration && + root.FindNode(fieldLocation.SourceSpan) is FieldDeclarationSyntax fieldDeclaration) + { + // Get the semantic model, as we need to resolve symbols + SemanticModel semanticModel = (await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false))!; + + // Register the code fix to update the semi-auto property to a partial property + context.RegisterCodeFix( + CodeAction.Create( + title: "Use a partial property", + createChangedDocument: token => ConvertToPartialProperty(context.Document, semanticModel, root, propertyDeclaration, fieldDeclaration), + equivalenceKey: "Use a partial property"), + diagnostic); + } + } + + /// + /// Tries to get an for the [GeneratedDependencyProperty] attribute. + /// + /// The original document being fixed. + /// The instance for the current compilation. + /// The resulting attribute list, if successfully retrieved. + /// Whether could be retrieved successfully. + private static bool TryGetGeneratedObservablePropertyAttributeList( + Document document, + SemanticModel semanticModel, + [NotNullWhen(true)] out AttributeListSyntax? observablePropertyAttributeList) + { + // Make sure we can resolve the '[GeneratedDependencyProperty]' attribute + if (semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute) is not INamedTypeSymbol attributeSymbol) + { + observablePropertyAttributeList = null; + + return false; + } + + SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(document); + + // Create the attribute syntax for the new '[GeneratedDependencyProperty]' attribute here too + SyntaxNode attributeTypeSyntax = syntaxGenerator.TypeExpression(attributeSymbol).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation); + + observablePropertyAttributeList = (AttributeListSyntax)syntaxGenerator.Attribute(attributeTypeSyntax); + + return true; + } + + /// + /// Applies the code fix to a target identifier and returns an updated document. + /// + /// The original document being fixed. + /// The instance for the current compilation. + /// The original tree root belonging to the current document. + /// The for the property being updated. + /// The for the declared property to remove. + /// An updated document with the applied code fix, and being replaced with a partial property. + private static async Task ConvertToPartialProperty( + Document document, + SemanticModel semanticModel, + SyntaxNode root, + PropertyDeclarationSyntax propertyDeclaration, + FieldDeclarationSyntax fieldDeclaration) + { + await Task.CompletedTask; + + // If we can't generate the new attribute list, bail (this should never happen) + if (!TryGetGeneratedObservablePropertyAttributeList(document, semanticModel, out AttributeListSyntax? observablePropertyAttributeList)) + { + return document; + } + + // Create an editor to perform all mutations + SyntaxEditor syntaxEditor = new(root, document.Project.Solution.Workspace.Services); + + ConvertToPartialProperty( + propertyDeclaration, + fieldDeclaration, + observablePropertyAttributeList, + syntaxEditor); + + // Create the new document with the single change + return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); + } + + /// + /// Applies the code fix to a target identifier and returns an updated document. + /// + /// The for the property being updated. + /// The for the declared property to remove. + /// The with the attribute to add. + /// The instance to use. + /// An updated document with the applied code fix, and being replaced with a partial property. + private static void ConvertToPartialProperty( + PropertyDeclarationSyntax propertyDeclaration, + FieldDeclarationSyntax fieldDeclaration, + AttributeListSyntax observablePropertyAttributeList, + SyntaxEditor syntaxEditor) + { + // Start setting up the updated attribute lists + SyntaxList attributeLists = propertyDeclaration.AttributeLists; + + if (attributeLists is [AttributeListSyntax firstAttributeListSyntax, ..]) + { + // Remove the trivia from the original first attribute + attributeLists = attributeLists.Replace( + nodeInList: firstAttributeListSyntax, + newNode: firstAttributeListSyntax.WithoutTrivia()); + + // If the property has at least an attribute list, move the trivia from it to the new attribute + observablePropertyAttributeList = observablePropertyAttributeList.WithTriviaFrom(firstAttributeListSyntax); + + // Insert the new attribute + attributeLists = attributeLists.Insert(0, observablePropertyAttributeList); + } + else + { + // Otherwise (there are no attribute lists), transfer the trivia to the new (only) attribute list + observablePropertyAttributeList = observablePropertyAttributeList.WithTriviaFrom(propertyDeclaration); + + // Save the new attribute list + attributeLists = attributeLists.Add(observablePropertyAttributeList); + } + + // Get a new property that is partial and with semicolon token accessors + PropertyDeclarationSyntax updatedPropertyDeclaration = + propertyDeclaration + .AddModifiers(Token(SyntaxKind.PartialKeyword)) + .WithoutLeadingTrivia() + .WithAttributeLists(attributeLists) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithAccessorList(AccessorList(List( + [ + // Keep the accessors (so we can easily keep all trivia, modifiers, attributes, etc.) but make them semicolon only + propertyDeclaration.AccessorList!.Accessors[0] + .WithBody(null) + .WithExpressionBody(null) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .WithAdditionalAnnotations(Formatter.Annotation), + propertyDeclaration.AccessorList!.Accessors[1] + .WithBody(null) + .WithExpressionBody(null) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .WithTrailingTrivia(propertyDeclaration.AccessorList.Accessors[1].GetTrailingTrivia()) + .WithAdditionalAnnotations(Formatter.Annotation) + ])).WithTrailingTrivia(propertyDeclaration.AccessorList.GetTrailingTrivia())); + + syntaxEditor.ReplaceNode(propertyDeclaration, updatedPropertyDeclaration); + + // Also remove the field declaration (it'll be generated now) + syntaxEditor.RemoveNode(fieldDeclaration); + + // Find the parent type for the property + TypeDeclarationSyntax typeDeclaration = propertyDeclaration.FirstAncestorOrSelf()!; + + // Make sure it's partial (we create the updated node in the function to preserve the updated property declaration). + // If we created it separately and replaced it, the whole tree would also be replaced, and we'd lose the new property. + if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + syntaxEditor.ReplaceNode(typeDeclaration, static (node, generator) => generator.WithModifiers(node, generator.GetModifiers(node).WithPartial(true))); + } + } + + /// + /// A custom with the logic from . + /// + private sealed class FixAllProvider : DocumentBasedFixAllProvider + { + /// + protected override async Task FixAllAsync(FixAllContext fixAllContext, Document document, ImmutableArray diagnostics) + { + // Get the semantic model, as we need to resolve symbols + if (await document.GetSemanticModelAsync(fixAllContext.CancellationToken).ConfigureAwait(false) is not SemanticModel semanticModel) + { + return document; + } + + // Get the document root (this should always succeed) + if (await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false) is not SyntaxNode root) + { + return document; + } + + // If we can't generate the new attribute list, bail (this should never happen) + if (!TryGetGeneratedObservablePropertyAttributeList(document, semanticModel, out AttributeListSyntax? observablePropertyAttributeList)) + { + return document; + } + + // Create an editor to perform all mutations (across all edits in the file) + SyntaxEditor syntaxEditor = new(root, fixAllContext.Solution.Services); + + foreach (Diagnostic diagnostic in diagnostics) + { + // Get the current property declaration for the diagnostic + if (root.FindNode(diagnostic.Location.SourceSpan) is not PropertyDeclarationSyntax propertyDeclaration) + { + continue; + } + + // Also check that we can find the target field to remove + if (diagnostic.AdditionalLocations is not [{ } fieldLocation] || + root.FindNode(fieldLocation.SourceSpan) is not FieldDeclarationSyntax fieldDeclaration) + { + continue; + } + + ConvertToPartialProperty( + propertyDeclaration, + fieldDeclaration, + observablePropertyAttributeList, + syntaxEditor); + } + + return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); + } + } +} From df50c2f325d3cd71fda71ba05734c1d4696357a7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 19:55:06 -0800 Subject: [PATCH 067/126] Add 'CSharpCodeFixTest<,>' type --- ...t.DependencyPropertyGenerator.Tests.csproj | 3 +- .../Helpers/CSharpAnalyzerTest{TAnalyzer}.cs | 2 +- ...harpCodeFixerTest{TAnalyzer,TCodeFixer}.cs | 42 +++++++++++++++++++ .../Test_Analyzers.cs | 2 +- 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index 7828f4b89..5b4fbb504 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -19,7 +19,8 @@ - + + diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs index 10478a2bc..c85c520a1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs @@ -13,7 +13,7 @@ using Windows.UI.Xaml; using CommunityToolkit.WinUI; -namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers; +namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; /// /// A custom that uses a specific C# language version to parse code. diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs new file mode 100644 index 000000000..6bbc2a8f0 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs @@ -0,0 +1,42 @@ +// 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.GeneratedDependencyProperty.Tests.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. +internal sealed class CSharpCodeFixTest : CSharpCodeFixTest + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFixer : CodeFixProvider, 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 CSharpCodeFixTest(LanguageVersion languageVersion) + { + this.languageVersion = languageVersion; + } + + /// + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(this.languageVersion, DocumentationMode.Diagnose); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 726bacd3f..d8305a64a 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers; +using CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; using Microsoft.CodeAnalysis.CSharp; using Microsoft.VisualStudio.TestTools.UnitTesting; From 7f57e6c78f919143e17800e8e9e8ecef517ccfb8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 20:20:05 -0800 Subject: [PATCH 068/126] Add basic code fixer test --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 2 +- ...t.DependencyPropertyGenerator.Tests.csproj | 1 + ...ndencyPropertyOnManualPropertyCodeFixer.cs | 86 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 3b1ca0133..d9f15e63a 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -20,7 +20,7 @@ using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace CommunityToolkit.Mvvm.CodeFixers; +namespace CommunityToolkit.GeneratedDependencyProperty; /// /// A code fixer that converts manual properties into partial properties using [GeneratedDependencytProperty]. diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index 5b4fbb504..ea88d28b9 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs new file mode 100644 index 000000000..9e6d81b9f --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -0,0 +1,86 @@ +// 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.WinUI; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using CSharpCodeFixTest = CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers.CSharpCodeFixTest< + CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyAnalyzer, + CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyCodeFixer>; +using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier< + CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyAnalyzer, + CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyCodeFixer, + Microsoft.CodeAnalysis.Testing.DefaultVerifier>; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +[TestClass] +public class Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer +{ + [TestMethod] + [DataRow("string", "string")] + [DataRow("string", "string?")] + [DataRow("object", "object")] + [DataRow("object", "object?")] + [DataRow("int", "int")] + [DataRow("int?", "int?")] + public async Task SimpleProperty(string underlyingType, string propertyType) + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: nameof(Name), + propertyType: typeof({{underlyingType}}), + ownerType: typeof(MyControl), + typeMetadata: null); + + public {{propertyType}} [|Name|] + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + TestState = { AdditionalReferences = + { + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + }} + }; + + await test.RunAsync(); + } +} From 866492bce5a274b88cf4dbaf5e8f1727ef8064f9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 21:19:05 -0800 Subject: [PATCH 069/126] Optimize registration for default values --- .../DependencyPropertyGenerator.Execute.cs | 2 +- .../Test_DependencyPropertyGenerator.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 3da03d7d2..4c5c82c5b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -436,7 +436,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) string typeMetadata = propertyInfo switch { // Shared codegen - { DefaultValue: DependencyPropertyDefaultValue.Null, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + { DefaultValue: DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $""" diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index cfe303d5c..940ea53f7 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -45,7 +45,7 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int))); + typeMetadata: null); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] @@ -464,7 +464,7 @@ partial class MyControl name: "Number", propertyType: typeof(int), ownerType: typeof(MyControl), - typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata(default(int))); + typeMetadata: null); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] From 26fdeed582a9b57004689c31ea2002876d3dd6ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 22:21:00 -0800 Subject: [PATCH 070/126] Handle more default properties in analyzer --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 55 +++++++++++++++++- .../Extensions/IOperationExtensions.cs | 56 +++++++++++++++++++ .../Models/TypedConstantInfo.Factory.cs | 48 ++++++++++++++++ .../Test_Analyzers.cs | 52 ++++++++++++++++- 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IOperationExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 3a6dbe99e..5d4dfbf58 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -9,6 +9,7 @@ using CommunityToolkit.GeneratedDependencyProperty.Constants; using CommunityToolkit.GeneratedDependencyProperty.Extensions; using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using CommunityToolkit.GeneratedDependencyProperty.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -50,6 +51,11 @@ public sealed class UseGeneratedDependencyPropertyOnManualPropertyAnalyzer : Dia /// private static readonly ObjectPool> FieldFlagsStackPool = new(CreateFlagsStack); + /// + /// The property name for the serialized property value, if present. + /// + public const string DefaultValuePropertyName = "DefaultValue"; + /// public override ImmutableArray SupportedDiagnostics { get; } = [UseGeneratedDependencyPropertyForManualProperty]; @@ -386,10 +392,48 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla return; } - // For now, just check that the metadata is 'null' + // First, check if the metadata is 'null' (simplest case) if (propertyMetadataArgument.Value.ConstantValue is not { HasValue: true, Value: null }) { - return; + // Next, check if the argument is 'new PropertyMetadata(...)' with the default value for the property type + if (propertyMetadataArgument.Value is not IObjectCreationOperation { Arguments: [{ } defaultValueArgument] } objectCreationOperation) + { + return; + } + + // Make sure the object being created is actually 'PropertyMetadata' + if (!SymbolEqualityComparer.Default.Equals(objectCreationOperation.Type, propertyMetadataSymbol)) + { + return; + } + + // The argument should be a conversion operation (boxing) + if (defaultValueArgument.Value is not IConversionOperation { IsTryCast: false, Type.SpecialType: SpecialType.System_Object } conversionOperation) + { + return; + } + + // Check whether the value is a default constant value. + // If it is, then the property is valid (no explicit value). + if (!conversionOperation.Operand.IsConstantValueDefault()) + { + // If that is not the case, check if it's some constant value we can forward + if (!TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue)) + { + // As a last resort, check if this is explicitly a 'default(T)' expression + if (conversionOperation.Operand is not IDefaultValueOperation { Type: { } defaultValueExpressionType }) + { + return; + } + + // Also make sure the type matches the property type (it's not technically guaranteed). + // If this succeeds, we can safely convert the property, the generated code will be fine. + if (!SymbolEqualityComparer.Default.Equals(defaultValueExpressionType, propertyTypeSymbol)) + { + return; + } + } + } } // Find the parent field for the operation (we're guaranteed to only fine one) @@ -448,6 +492,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla UseGeneratedDependencyPropertyForManualProperty, pair.Key.Locations.FirstOrDefault(), [fieldLocation], + ImmutableDictionary.Create().Add(DefaultValuePropertyName, fieldFlags.DefaultValue?.ToString()), pair.Key)); } } @@ -467,6 +512,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla { fieldFlags.PropertyName = null; fieldFlags.PropertyType = null; + fieldFlags.DefaultValue = null; fieldFlags.FieldLocation = null; fieldFlagsStack.Push(fieldFlags); @@ -536,6 +582,11 @@ private sealed class FieldFlags /// public ITypeSymbol? PropertyType; + /// + /// The default value to use (not present if it does not need to be set explicitly). + /// + public TypedConstantInfo? DefaultValue; + /// /// The location of the target field being initialized. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IOperationExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IOperationExtensions.cs new file mode 100644 index 000000000..a6d7ba672 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/IOperationExtensions.cs @@ -0,0 +1,56 @@ +// 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 Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for types. +/// +internal static class IOperationExtensions +{ + /// + /// Checks whether a given operation represents a default constant value. + /// + /// The input instance. + /// Whether represents a default constant value. + public static bool IsConstantValueDefault(this IOperation operation) + { + if (operation is not { Type: not null, ConstantValue.HasValue: true }) + { + return false; + } + + // Easy check for reference types + if (operation is { Type.IsReferenceType: true, ConstantValue.Value: null }) + { + return true; + } + + // Equivalent check for nullable value types too + if (operation is { Type.SpecialType: SpecialType.System_Nullable_T, ConstantValue.Value: null }) + { + return true; + } + + // Manually match for known primitive types + return (operation.Type.SpecialType, operation.ConstantValue.Value) switch + { + (SpecialType.System_Byte, default(byte)) or + (SpecialType.System_Char, default(char)) or + (SpecialType.System_Int16, default(short)) or + (SpecialType.System_UInt16, default(ushort)) or + (SpecialType.System_Int32, default(int)) or + (SpecialType.System_UInt32, default(uint)) or + (SpecialType.System_Int64, default(long)) or + (SpecialType.System_UInt64, default(ulong)) or + (SpecialType.System_Boolean, default(bool)) => true, + (SpecialType.System_Single, float x) when BitConverter.DoubleToInt64Bits(x) == 0 => true, + (SpecialType.System_Double, double x) when BitConverter.DoubleToInt64Bits(x) == 0 => true, + _ => false + }; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index bb030884c..e98111cde 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; @@ -57,4 +58,51 @@ public static TypedConstantInfo Create(TypedConstant arg) _ => throw new ArgumentException("Invalid typed constant type"), }; } + + /// + /// Creates a new instance from a given value. + /// + /// The input value. + /// A instance representing . + /// Thrown if the input argument is not valid. + public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out TypedConstantInfo? result) + { + // Validate that we do have some constant value + if (operation is not { Type: { } operationType, ConstantValue.HasValue: true }) + { + result = null; + + return false; + } + + if (operation.ConstantValue.Value is null) + { + result = new Null(); + + return true; + } + + // Handle all known possible constant values + result = (operationType, operation.ConstantValue.Value) switch + { + ({ SpecialType: SpecialType.System_String }, string text) => new Primitive.String(text), + ({ SpecialType: SpecialType.System_Boolean}, bool flag) => new Primitive.Boolean(flag), + (_, byte b) => new Primitive.Of(b), + (_, char c) => new Primitive.Of(c), + (_, double d) => new Primitive.Of(d), + (_, float f) => new Primitive.Of(f), + (_, int i) => new Primitive.Of(i), + (_, long l) => new Primitive.Of(l), + (_, sbyte sb) => new Primitive.Of(sb), + (_, short sh) => new Primitive.Of(sh), + (_, uint ui) => new Primitive.Of(ui), + (_, ulong ul) => new Primitive.Of(ul), + (_, ushort ush) => new Primitive.Of(ush), + (_, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), + (INamedTypeSymbol { TypeKind: TypeKind.Enum}, object value) => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), + _ => throw new ArgumentException("Invalid typed constant type"), + }; + + return true; + } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index d8305a64a..e7b2e7f27 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1289,7 +1289,6 @@ public string? Name [DataRow("\"Name\"", "typeof(string)", "typeof(string)", "null")] [DataRow("\"Name\"", "typeof(string)", "typeof(Control)", "null")] [DataRow("\"Name\"", "typeof(string)", "typeof(DependencyObject)", "null")] - [DataRow("\"Name\"", "typeof(string)", "typeof(MyControl)", "new PropertyMetadata(42)")] [DataRow("\"Name\"", "typeof(string)", "typeof(MyControl)", "new PropertyMetadata(null, (d, e) => { })")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_InvalidRegisterArguments_DoesNotWarn( string name, @@ -1423,4 +1422,55 @@ public partial class MyControl : Control await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + [DataRow("string", "string", "null")] + [DataRow("string", "string", "default(string)")] + [DataRow("string", "string", "(string)null")] + [DataRow("string", "string", "\"\"")] + [DataRow("string", "string", "\"Hello\"")] + [DataRow("string", "string?", "null")] + [DataRow("object", "object", "null")] + [DataRow("object", "object?", "null")] + [DataRow("int", "int", "0")] + [DataRow("int", "int", "42")] + [DataRow("int", "int", "default(int)")] + [DataRow("int?", "int?", "null")] + [DataRow("int?", "int?", "0")] + [DataRow("int?", "int?", "42")] + [DataRow("int?", "int?", "default(int?)")] + [DataRow("int?", "int?", "null")] + [DataRow("System.TimeSpan", "System.TimeSpan", "default(System.TimeSpan)")] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_ExplicitDefaultValue_Warns( + string dependencyPropertyType, + string propertyType, + string defaultValueExpression) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{defaultValueExpression}})); + + public {{propertyType}} {|WCTDP0017:Name|} + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From bebdcacc211fbab45bce3dabfdcad277a770ea83 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 11 Dec 2024 23:14:31 -0800 Subject: [PATCH 071/126] Handle even more default properties in analyzer --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 41 ++++- ...endencyPropertyOnManualPropertyAnalyzer.cs | 24 ++- .../Test_Analyzers.cs | 7 - ...ndencyPropertyOnManualPropertyCodeFixer.cs | 145 +++++++++++++++++- 4 files changed, 197 insertions(+), 20 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index d9f15e63a..c7dbf7ec7 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -56,6 +56,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } + // Retrieve the properties passed by the analyzer + string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName]; + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); // Get the property declaration and the field declaration from the target diagnostic @@ -69,7 +72,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix( CodeAction.Create( title: "Use a partial property", - createChangedDocument: token => ConvertToPartialProperty(context.Document, semanticModel, root, propertyDeclaration, fieldDeclaration), + createChangedDocument: token => ConvertToPartialProperty( + context.Document, + semanticModel, + root, + propertyDeclaration, + fieldDeclaration, + defaultValue), equivalenceKey: "Use a partial property"), diagnostic); } @@ -113,13 +122,15 @@ private static bool TryGetGeneratedObservablePropertyAttributeList( /// The original tree root belonging to the current document. /// The for the property being updated. /// The for the declared property to remove. + /// The expression for the default value of the property, if present /// An updated document with the applied code fix, and being replaced with a partial property. private static async Task ConvertToPartialProperty( Document document, SemanticModel semanticModel, SyntaxNode root, PropertyDeclarationSyntax propertyDeclaration, - FieldDeclarationSyntax fieldDeclaration) + FieldDeclarationSyntax fieldDeclaration, + string? defaultValueExpression) { await Task.CompletedTask; @@ -136,7 +147,8 @@ private static async Task ConvertToPartialProperty( propertyDeclaration, fieldDeclaration, observablePropertyAttributeList, - syntaxEditor); + syntaxEditor, + defaultValueExpression); // Create the new document with the single change return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); @@ -149,13 +161,28 @@ private static async Task ConvertToPartialProperty( /// The for the declared property to remove. /// The with the attribute to add. /// The instance to use. + /// The expression for the default value of the property, if present /// An updated document with the applied code fix, and being replaced with a partial property. private static void ConvertToPartialProperty( PropertyDeclarationSyntax propertyDeclaration, FieldDeclarationSyntax fieldDeclaration, AttributeListSyntax observablePropertyAttributeList, - SyntaxEditor syntaxEditor) + SyntaxEditor syntaxEditor, + string? defaultValueExpression) { + // If we do have a default value expression, set it in the attribute. + // We extract the generated attribute so we can add the new argument. + // It's important to reuse it, as it has the "add usings" annotation. + if (defaultValueExpression is not null) + { + observablePropertyAttributeList = + AttributeList(SingletonSeparatedList( + observablePropertyAttributeList.Attributes[0] + .AddArgumentListArguments( + AttributeArgument(ParseExpression(defaultValueExpression)) + .WithNameEquals(NameEquals(IdentifierName("DefaultValue")))))); + } + // Start setting up the updated attribute lists SyntaxList attributeLists = propertyDeclaration.AttributeLists; @@ -264,11 +291,15 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider continue; } + // Retrieve the properties passed by the analyzer + string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName]; + ConvertToPartialProperty( propertyDeclaration, fieldDeclaration, observablePropertyAttributeList, - syntaxEditor); + syntaxEditor, + defaultValue); } return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 5d4dfbf58..b85e5dbeb 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -413,14 +413,26 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla return; } - // Check whether the value is a default constant value. - // If it is, then the property is valid (no explicit value). - if (!conversionOperation.Operand.IsConstantValueDefault()) + bool isNullableValueType = propertyTypeSymbol is INamedTypeSymbol { IsValueType: true, IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + + // Check whether the value is a default constant value. If it is, then the property is valid (no explicit value). + // We need to special case nullable value types, as the default value for the underlying type is not the actual default. + if (!conversionOperation.Operand.IsConstantValueDefault() || isNullableValueType) { - // If that is not the case, check if it's some constant value we can forward - if (!TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue)) + // The value is just 'null' with no type, special case this one and skip the other checks below + if (conversionOperation.Operand is { Type: null, ConstantValue: { HasValue: true, Value: null } }) + { + // This is only allowed for reference or nullable types. This 'null' is redundant, but support it nonetheless. + // It's not that uncommon for especially legacy codebases to have this kind of pattern in dependency properties. + if (!propertyTypeSymbol.IsReferenceType && !isNullableValueType) + { + return; + } + } + else if (!TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue)) { - // As a last resort, check if this is explicitly a 'default(T)' expression + // If that is not the case, check if it's some constant value we can forward. In this case, we did not + // retrieve it. As a last resort, check if this is explicitly a 'default(T)' expression. if (conversionOperation.Operand is not IDefaultValueOperation { Type: { } defaultValueExpressionType }) { return; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index e7b2e7f27..82f98a78b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1225,7 +1225,6 @@ public partial class MyControl : Control public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_FieldNotInitialized_DoesNotWarn() { const string source = """ - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1252,7 +1251,6 @@ public string? Name public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_FieldWithDifferentName_DoesNotWarn() { const string source = """ - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1297,7 +1295,6 @@ public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_Invalid string typeMetadata) { string source = $$""" - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1328,7 +1325,6 @@ public string? Name public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_MissingGetter_DoesNotWarn() { const string source = """ - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1358,7 +1354,6 @@ public string? Name public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_MissingSetter_DoesNotWarn() { const string source = """ - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1396,7 +1391,6 @@ public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidPr string propertyType) { string source = $$""" - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -1447,7 +1441,6 @@ public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidPr string defaultValueExpression) { string source = $$""" - using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 9e6d81b9f..b975b8cb7 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -30,7 +30,7 @@ public class Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer [DataRow("object", "object?")] [DataRow("int", "int")] [DataRow("int?", "int?")] - public async Task SimpleProperty(string underlyingType, string propertyType) + public async Task SimpleProperty(string dependencyPropertyType, string propertyType) { string original = $$""" using Windows.UI.Xaml; @@ -42,7 +42,7 @@ public class MyControl : Control { public static readonly DependencyProperty NameProperty = DependencyProperty.Register( name: nameof(Name), - propertyType: typeof({{underlyingType}}), + propertyType: typeof({{dependencyPropertyType}}), ownerType: typeof(MyControl), typeMetadata: null); @@ -83,4 +83,145 @@ public partial class MyControl : Control await test.RunAsync(); } + + [TestMethod] + [DataRow("string", "string", "null")] + [DataRow("string", "string", "default(string)")] + [DataRow("string", "string", "(string)null")] + [DataRow("string", "string?", "null")] + [DataRow("object", "object", "null")] + [DataRow("object", "object?", "null")] + [DataRow("int", "int", "0")] + [DataRow("int", "int", "default(int)")] + [DataRow("int?", "int?", "null")] + [DataRow("int?", "int?", "default(int?)")] + [DataRow("int?", "int?", "null")] + [DataRow("System.TimeSpan", "System.TimeSpan", "default(System.TimeSpan)")] + public async Task SimpleProperty_WithExplicitValue_DefaultValue( + string dependencyPropertyType, + string propertyType, + string defaultValueExpression) + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{defaultValueExpression}})); + + public {{propertyType}} [|Name|] + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + TestState = { AdditionalReferences = + { + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + }} + }; + + await test.RunAsync(); + } + + [TestMethod] + [DataRow("string", "string", "\"\"")] + [DataRow("string", "string", "\"Hello\"")] + [DataRow("int", "int", "42")] + [DataRow("int?", "int?", "0")] + [DataRow("int?", "int?", "42")] + public async Task SimpleProperty_WithExplicitValue_NotDefault( + string dependencyPropertyType, + string propertyType, + string defaultValueExpression) + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{defaultValueExpression}})); + + public {{propertyType}} [|Name|] + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = {{defaultValueExpression}})] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + TestState = { AdditionalReferences = + { + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + }} + }; + + await test.RunAsync(); + } } From be37f5f3c2ab9823115e219ef17ac5caf999f5a3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Dec 2024 16:12:01 -0800 Subject: [PATCH 072/126] Add more generator unit tests --- .../Test_DependencyPropertyGenerator.cs | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 940ea53f7..6162b1442 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -2699,4 +2699,180 @@ public partial string? Name CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); } + + [TestMethod] + + // The 'string' and 'object' types are special + [DataRow("string", "string", "null")] + [DataRow("string", "string?", "null")] + [DataRow("object", "object", "null")] + [DataRow("object", "object?", "null")] + + // Well known WinRT primitive types + [DataRow("int", "int", "null")] + [DataRow("byte", "byte", "null")] + [DataRow("sbyte", "sbyte", "null")] + [DataRow("short", "short", "null")] + [DataRow("ushort", "ushort", "null")] + [DataRow("uint", "uint", "null")] + [DataRow("long", "long", "null")] + [DataRow("ulong", "ulong", "null")] + [DataRow("char", "char", "null")] + [DataRow("float", "float", "null")] + [DataRow("double", "double", "null")] + + // Well known WinRT struct types + [DataRow("Matrix3x2", "Matrix3x2", "null")] + [DataRow("Matrix4x4", "Matrix4x4", "null")] + [DataRow("Plane", "Plane", "null")] + [DataRow("Quaternion", "Quaternion", "null")] + [DataRow("Vector2", "Vector2", "null")] + [DataRow("Vector3", "Vector3", "null")] + [DataRow("Vector4", "Vector4", "null")] + [DataRow("Point", "Point", "null")] + [DataRow("Rect", "Rect", "null")] + + // Well known WinRT enum types + [DataRow("Visibility", "Visibility", "null")] + + // Nullable types, they're always just 'null' + [DataRow("int?", "int?", "null")] + [DataRow("byte?", "byte?", "null")] + [DataRow("char?", "char?", "null")] + [DataRow("long?", "long?", "null")] + [DataRow("float?", "float?", "null")] + [DataRow("double?", "double?", "null")] + [DataRow("DateTimeOffset?", "DateTimeOffset?", "null")] + [DataRow("TimeSpan?", "TimeSpan?", "null")] + [DataRow("Guid?", "Guid?", "null")] + [DataRow("KeyValuePair?", "KeyValuePair?", "null")] + + // Custom struct types + [DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public int X; }")] + [DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public string X { get; set; }")] + + // Custom enum types + [DataRow("MyEnum", "MyEnum", "new PropertyMetadata(default(MyEnum))", "public enum MyEnum { A, B, C }")] + public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized( + string dependencyPropertyType, + string propertyType, + string propertyMetadataExpression, + string? typeDefinition = "") + { + string source = $$""" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + #nullable enable + + namespace MyNamespace; + + {{typeDefinition}} + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial {{propertyType}} Name { get; set; } + } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: {{propertyMetadataExpression}}); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial {{propertyType}} Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + {{propertyType}} __unboxedValue = ({{propertyType}})__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref {{propertyType}} propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref {{propertyType}} propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged({{propertyType}} newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); + } } From 26d8f033ecbba7aa4104024f50286d038647b5de Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Dec 2024 17:49:55 -0800 Subject: [PATCH 073/126] Handle supported default value types --- .../DependencyPropertyGenerator.Execute.cs | 41 ++++++++- .../Extensions/ITypeSymbolExtensions.cs | 47 ++++++++++ .../Models/DependencyPropertyDefaultValue.cs | 3 +- .../CSharpGeneratorTest{TGenerator}.cs | 2 + .../Test_DependencyPropertyGenerator.cs | 86 ++++++++++--------- 5 files changed, 135 insertions(+), 44 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 4c5c82c5b..1cb241624 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -322,7 +322,44 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // First we need to special case non nullable values, as for those we need 'default'. if (propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }) { - return new DependencyPropertyDefaultValue.Default(propertySymbol.Type.GetFullyQualifiedName()); + string fullyQualifiedTypeName = propertySymbol.Type.GetFullyQualifiedName(); + + // There is a special case for this: if the type of the property is a built-in WinRT + // projected enum type or struct type (ie. some projected value type in general, except + // for 'Nullable' values), then we can just use 'null' and bypass creating the property + // metadata. The WinRT runtime will automatically instantiate a default value for us. + if (propertySymbol.Type.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) || + propertySymbol.Type.IsContainedInNamespace("Windows.Foundation.Numerics")) + { + return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); + } + + // Special case a few more well known value types that are mapped for WinRT + if (propertySymbol.Type.Name is "Point" or "Rect" or "Size" && + propertySymbol.Type.IsContainedInNamespace("Windows.Foundation")) + { + return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); + } + + // Lastly, special case the well known primitive types + if (propertySymbol.Type.SpecialType is + SpecialType.System_Int32 or + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Char or + SpecialType.System_Single or + SpecialType.System_Double) + { + return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); + } + + // In all other cases, just use 'default(T)' here + return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: false); } // For all other ones, we can just use the 'null' placeholder again @@ -436,7 +473,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) string typeMetadata = propertyInfo switch { // Shared codegen - { DefaultValue: DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } + { DefaultValue: DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default(_, true), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => "null", { DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false } => $""" diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index eba208177..873b5fc43 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -136,4 +136,51 @@ static void BuildFrom(ISymbol? symbol, ref readonly ImmutableArrayBuilder BuildFrom(symbol, in builder); } + + /// + /// Checks whether a given type is contained in a namespace with a specified name. + /// + /// The input instance. + /// The namespace to check. + /// Whether is contained within . + public static bool IsContainedInNamespace(this ITypeSymbol symbol, string? namespaceName) + { + static void BuildFrom(INamespaceSymbol? symbol, ref readonly ImmutableArrayBuilder builder) + { + switch (symbol) + { + // Namespaces that are nested also append a leading '.' + case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }: + BuildFrom(symbol.ContainingNamespace, in builder); + builder.Add('.'); + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + + // Other namespaces (i.e. the one right before global) skip the leading '.' + case INamespaceSymbol { IsGlobalNamespace: false }: + builder.AddRange(symbol.MetadataName.AsSpan()); + break; + default: + break; + } + } + + // Special case for no containing namespace + if (symbol.ContainingNamespace is not { } containingNamespace) + { + return namespaceName is null; + } + + // Special case if the type is directly in the global namespace + if (containingNamespace.IsGlobalNamespace) + { + return containingNamespace.MetadataName == namespaceName; + } + + using ImmutableArrayBuilder builder = new(); + + BuildFrom(containingNamespace, in builder); + + return builder.WrittenSpan.SequenceEqual(namespaceName.AsSpan()); + } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs index b494f4c5c..29469028e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs @@ -27,7 +27,8 @@ public override string ToString() /// A type representing default value for a specific type. /// /// The input type name. - public sealed record Default(string TypeName) : DependencyPropertyDefaultValue + /// Indicates whether the type is projected, meaning WinRT can default initialize it automatically if needed. + public sealed record Default(string TypeName, bool IsProjectedType) : DependencyPropertyDefaultValue { /// public override string ToString() diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs index 5390d1858..027198c79 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.Foundation; using Windows.UI.ViewManagement; using Windows.UI.Xaml; @@ -178,6 +179,7 @@ private static CSharpCompilation CreateCompilation(string source, LanguageVersio IEnumerable metadataReferences = [ .. Net80.References.All, + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 6162b1442..7836c6e5c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -2702,64 +2702,68 @@ public partial string? Name [TestMethod] - // The 'string' and 'object' types are special - [DataRow("string", "string", "null")] - [DataRow("string", "string?", "null")] - [DataRow("object", "object", "null")] - [DataRow("object", "object?", "null")] + // The 'string' type is special + [DataRow("string", "string", "object", "null")] + [DataRow("string", "string?", "object?", "null")] // Well known WinRT primitive types - [DataRow("int", "int", "null")] - [DataRow("byte", "byte", "null")] - [DataRow("sbyte", "sbyte", "null")] - [DataRow("short", "short", "null")] - [DataRow("ushort", "ushort", "null")] - [DataRow("uint", "uint", "null")] - [DataRow("long", "long", "null")] - [DataRow("ulong", "ulong", "null")] - [DataRow("char", "char", "null")] - [DataRow("float", "float", "null")] - [DataRow("double", "double", "null")] + [DataRow("int", "int", "object", "null")] + [DataRow("byte", "byte", "object", "null")] + [DataRow("sbyte", "sbyte", "object", "null")] + [DataRow("short", "short", "object", "null")] + [DataRow("ushort", "ushort", "object", "null")] + [DataRow("uint", "uint", "object", "null")] + [DataRow("long", "long", "object", "null")] + [DataRow("ulong", "ulong", "object", "null")] + [DataRow("char", "char", "object", "null")] + [DataRow("float", "float", "object", "null")] + [DataRow("double", "double", "object", "null")] // Well known WinRT struct types - [DataRow("Matrix3x2", "Matrix3x2", "null")] - [DataRow("Matrix4x4", "Matrix4x4", "null")] - [DataRow("Plane", "Plane", "null")] - [DataRow("Quaternion", "Quaternion", "null")] - [DataRow("Vector2", "Vector2", "null")] - [DataRow("Vector3", "Vector3", "null")] - [DataRow("Vector4", "Vector4", "null")] - [DataRow("Point", "Point", "null")] - [DataRow("Rect", "Rect", "null")] + [DataRow("global::Windows.Foundation.Numerics.Matrix3x2", "global::Windows.Foundation.Numerics.Matrix3x2", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Matrix4x4", "global::Windows.Foundation.Numerics.Matrix4x4", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Plane", "global::Windows.Foundation.Numerics.Plane", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Quaternion", "global::Windows.Foundation.Numerics.Quaternion", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Vector2", "global::Windows.Foundation.Numerics.Vector2", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Vector3", "global::Windows.Foundation.Numerics.Vector3", "object", "null")] + [DataRow("global::Windows.Foundation.Numerics.Vector4", "global::Windows.Foundation.Numerics.Vector4", "object", "null")] + [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "object", "null")] + [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "object", "null")] + [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "object", "null")] // Well known WinRT enum types - [DataRow("Visibility", "Visibility", "null")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "object", "null")] // Nullable types, they're always just 'null' - [DataRow("int?", "int?", "null")] - [DataRow("byte?", "byte?", "null")] - [DataRow("char?", "char?", "null")] - [DataRow("long?", "long?", "null")] - [DataRow("float?", "float?", "null")] - [DataRow("double?", "double?", "null")] - [DataRow("DateTimeOffset?", "DateTimeOffset?", "null")] - [DataRow("TimeSpan?", "TimeSpan?", "null")] - [DataRow("Guid?", "Guid?", "null")] - [DataRow("KeyValuePair?", "KeyValuePair?", "null")] + [DataRow("int?", "int?", "object?", "null")] + [DataRow("byte?", "byte?", "object?", "null")] + [DataRow("char?", "char?", "object?", "null")] + [DataRow("long?", "long?", "object?", "null")] + [DataRow("float?", "float?", "object?", "null")] + [DataRow("double?", "double?", "object?", "null")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "object?", "null")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "object?", "null")] + [DataRow("global::System.Guid?", "global::System.Guid?", "object?", "null")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "object?", "null")] // Custom struct types - [DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public int X; }")] - [DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public string X { get; set; }")] + [DataRow("global::MyNamespace.MyStruct", "global::MyNamespace.MyStruct", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyStruct))", "public struct MyStruct { public int X; }")] + [DataRow("global::MyNamespace.MyStruct", "global::MyNamespace.MyStruct", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyStruct))", "public struct MyStruct { public string X { get; set; } }")] // Custom enum types - [DataRow("MyEnum", "MyEnum", "new PropertyMetadata(default(MyEnum))", "public enum MyEnum { A, B, C }")] + [DataRow("global::MyNamespace.MyEnum", "global::MyNamespace.MyEnum", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyEnum))", "public enum MyEnum { A, B, C }")] public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized( string dependencyPropertyType, string propertyType, + string defaultValueDefinition, string propertyMetadataExpression, string? typeDefinition = "") { string source = $$""" + using System; + using System.Collections.Generic; + using Windows.Foundation; + using Windows.Foundation.Numerics; using Windows.UI.Xaml; using CommunityToolkit.WinUI; @@ -2832,7 +2836,7 @@ public partial {{propertyType}} Name /// The raw property value that has been retrieved from . /// This method is invoked on the boxed value retrieved via on . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] - partial void OnNameGet(ref object? propertyValue); + partial void OnNameGet(ref {{defaultValueDefinition}} propertyValue); /// Executes the logic for when the accessor is invoked /// The unboxed property value that has been retrieved from . @@ -2844,7 +2848,7 @@ public partial {{propertyType}} Name /// The boxed property value that has been produced before assigning to . /// This method is invoked on the boxed value that is about to be passed to on . [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] - partial void OnNameSet(ref object? propertyValue); + partial void OnNameSet(ref {{defaultValueDefinition}} propertyValue); /// Executes the logic for when the accessor is invoked /// The property value that is being assigned to . From a3e4c026fb0e235b9f43aa2cbe71c03891f8bee7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Dec 2024 17:55:50 -0800 Subject: [PATCH 074/126] Remove unnecessary using directive --- ...UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index b975b8cb7..1a8f31def 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -13,10 +13,6 @@ using CSharpCodeFixTest = CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers.CSharpCodeFixTest< CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyAnalyzer, CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyCodeFixer>; -using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier< - CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyAnalyzer, - CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyCodeFixer, - Microsoft.CodeAnalysis.Testing.DefaultVerifier>; namespace CommunityToolkit.GeneratedDependencyProperty.Tests; From d8aee78e7bff6841f7e2a446f933957251132129 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Dec 2024 23:30:50 -0800 Subject: [PATCH 075/126] Fix handling of numerics, add more projected types --- .../DependencyPropertyGenerator.Execute.cs | 10 ++++++++-- .../Extensions/ITypeSymbolExtensions.cs | 10 ++++++++++ .../Test_DependencyPropertyGenerator.cs | 16 +++++++++------- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 1cb241624..f3dc16bc8 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -320,7 +320,7 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // In all other cases, we'll automatically use the default value of the type in question. // First we need to special case non nullable values, as for those we need 'default'. - if (propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }) + if (!propertySymbol.Type.IsDefaultValueNull()) { string fullyQualifiedTypeName = propertySymbol.Type.GetFullyQualifiedName(); @@ -329,7 +329,7 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // for 'Nullable' values), then we can just use 'null' and bypass creating the property // metadata. The WinRT runtime will automatically instantiate a default value for us. if (propertySymbol.Type.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) || - propertySymbol.Type.IsContainedInNamespace("Windows.Foundation.Numerics")) + propertySymbol.Type.IsContainedInNamespace("System.Numerics")) { return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); } @@ -341,6 +341,12 @@ public static DependencyPropertyDefaultValue GetDefaultValue( return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); } + // Special case two more system types + if (propertySymbol.Type is INamedTypeSymbol { MetadataName: "TimeSpan" or "DateTimeOffset", ContainingNamespace.MetadataName: "System" }) + { + return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); + } + // Lastly, special case the well known primitive types if (propertySymbol.Type.SpecialType is SpecialType.System_Int32 or diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 873b5fc43..261ddd64e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -13,6 +13,16 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; /// internal static class ITypeSymbolExtensions { + /// + /// Checks whether a given type has a default value of . + /// + /// The input instance to check. + /// Whether the default value of is . + public static bool IsDefaultValueNull(this ITypeSymbol symbol) + { + return symbol is { IsValueType: false } or INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; + } + /// /// Checks whether or not a given type symbol has a specified fully qualified metadata name. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 7836c6e5c..5d01fd0b8 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -2720,16 +2720,18 @@ public partial string? Name [DataRow("double", "double", "object", "null")] // Well known WinRT struct types - [DataRow("global::Windows.Foundation.Numerics.Matrix3x2", "global::Windows.Foundation.Numerics.Matrix3x2", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Matrix4x4", "global::Windows.Foundation.Numerics.Matrix4x4", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Plane", "global::Windows.Foundation.Numerics.Plane", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Quaternion", "global::Windows.Foundation.Numerics.Quaternion", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Vector2", "global::Windows.Foundation.Numerics.Vector2", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Vector3", "global::Windows.Foundation.Numerics.Vector3", "object", "null")] - [DataRow("global::Windows.Foundation.Numerics.Vector4", "global::Windows.Foundation.Numerics.Vector4", "object", "null")] + [DataRow("global::System.Numerics.Matrix3x2", "global::System.Numerics.Matrix3x2", "object", "null")] + [DataRow("global::System.Numerics.Matrix4x4", "global::System.Numerics.Matrix4x4", "object", "null")] + [DataRow("global::System.Numerics.Plane", "global::System.Numerics.Plane", "object", "null")] + [DataRow("global::System.Numerics.Quaternion", "global::System.Numerics.Quaternion", "object", "null")] + [DataRow("global::System.Numerics.Vector2", "global::System.Numerics.Vector2", "object", "null")] + [DataRow("global::System.Numerics.Vector3", "global::System.Numerics.Vector3", "object", "null")] + [DataRow("global::System.Numerics.Vector4", "global::System.Numerics.Vector4", "object", "null")] [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "object", "null")] [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "object", "null")] [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "object", "null")] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "object", "null")] + [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "object", "null")] // Well known WinRT enum types [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "object", "null")] From a7c51302b141c3885cb755d7a26cddfe835ed1d4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Dec 2024 23:46:20 -0800 Subject: [PATCH 076/126] Handle projected enums in analyzer, add tests --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 18 +++- .../Helpers/CSharpAnalyzerTest{TAnalyzer}.cs | 4 +- .../Test_Analyzers.cs | 89 ++++++++++++++++++- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index b85e5dbeb..037414fe0 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -429,10 +429,22 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla return; } } - else if (!TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue)) + else if (TypedConstantInfo.TryCreate(conversionOperation.Operand, out fieldFlags.DefaultValue)) { - // If that is not the case, check if it's some constant value we can forward. In this case, we did not - // retrieve it. As a last resort, check if this is explicitly a 'default(T)' expression. + // We have found a valid constant. As an optimization, we check whether the constant was the value + // of some projected built-in WinRT enum type (ie. not any user-defined enum type). If that is the + // case, the XAML infrastructure can default that values automatically, meaning we can skip the + // overhead of instantiating a 'PropertyMetadata' instance in code, and marshalling default value. + if (conversionOperation.Operand.Type is { TypeKind: TypeKind.Enum } operandType && + operandType.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml))) + { + fieldFlags.DefaultValue = null; + } + } + else + { + // If we don't have a constant, check if it's some constant value we can forward. In this case, we + // did not retrieve it. As a last resort, check if this is explicitly a 'default(T)' expression. if (conversionOperation.Operand is not IDefaultValueOperation { Type: { } defaultValueExpressionType }) { return; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs index c85c520a1..441d35d82 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs @@ -4,14 +4,15 @@ using System.Threading; using System.Threading.Tasks; +using CommunityToolkit.WinUI; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis; +using Windows.Foundation; using Windows.UI.ViewManagement; using Windows.UI.Xaml; -using CommunityToolkit.WinUI; namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; @@ -49,6 +50,7 @@ public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVe CSharpAnalyzerTest test = new(languageVersion) { TestCode = source }; test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Point).Assembly.Location)); test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location)); test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 82f98a78b..13f7dbbe5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1379,6 +1379,44 @@ public string? Name await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + [TestMethod] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "global::System.TimeSpan.FromSeconds(1)")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "global::System.TimeSpan.FromSeconds(1)")] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_ExplicitDefaultValue_DoesNotWarn( + string dependencyPropertyType, + string propertyType, + string defaultValueExpression) + { + string source = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{defaultValueExpression}})); + + public {{propertyType}} Name + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + [TestMethod] [DataRow("string", "string")] [DataRow("string", "string?")] @@ -1386,6 +1424,21 @@ public string? Name [DataRow("object", "object?")] [DataRow("int", "int")] [DataRow("int?", "int?")] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "null")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "default(global::System.TimeSpan?)")] + [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "null")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "default(global::System.DateTimeOffset?)")] + [DataRow("global::System.Guid?", "global::System.Guid?", "default(global::System.Guid?)")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "default(global::System.Collections.Generic.KeyValuePair?)")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "null")] + [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct", "default(global::MyApp.MyStruct)")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "null")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "default(global::MyApp.MyStruct?)")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum", "default(global::MyApp.MyEnum)")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "null")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "default(global::MyApp.MyEnum?)")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "null")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "default(global::MyApp.MyClass)")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_Warns( string dependencyPropertyType, string propertyType) @@ -1412,6 +1465,10 @@ public partial class MyControl : Control set => SetValue(NameProperty, value); } } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } + public class MyClass { } """; await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); @@ -1434,7 +1491,33 @@ public partial class MyControl : Control [DataRow("int?", "int?", "42")] [DataRow("int?", "int?", "default(int?)")] [DataRow("int?", "int?", "null")] - [DataRow("System.TimeSpan", "System.TimeSpan", "default(System.TimeSpan)")] + [DataRow("global::System.Numerics.Matrix3x2", "global::System.Numerics.Matrix3x2", "default(global::System.Numerics.Matrix3x2)")] + [DataRow("global::System.Numerics.Matrix4x4", "global::System.Numerics.Matrix4x4", "default(global::System.Numerics.Matrix4x4)")] + [DataRow("global::System.Numerics.Plane", "global::System.Numerics.Plane", "default(global::System.Numerics.Plane)")] + [DataRow("global::System.Numerics.Quaternion", "global::System.Numerics.Quaternion", "default(global::System.Numerics.Quaternion)")] + [DataRow("global::System.Numerics.Vector2", "global::System.Numerics.Vector2", "default(global::System.Numerics.Vector2)")] + [DataRow("global::System.Numerics.Vector3", "global::System.Numerics.Vector3", "default(global::System.Numerics.Vector3)")] + [DataRow("global::System.Numerics.Vector4", "global::System.Numerics.Vector4", "default(global::System.Numerics.Vector4)")] + [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "default(global::Windows.Foundation.Point)")] + [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "default(global::Windows.Foundation.Rect)")] + [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "default(global::Windows.Foundation.Size)")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "default(global::Windows.UI.Xaml.Visibility)")] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "default(System.TimeSpan)")] + [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "default(global::System.DateTimeOffset)")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "null")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "default(global::System.DateTimeOffset?)")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "default(global::System.TimeSpan?)")] + [DataRow("global::System.Guid?", "global::System.Guid?", "default(global::System.Guid?)")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "default(global::System.Collections.Generic.KeyValuePair?)")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "null")] + [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct", "default(global::MyApp.MyStruct)")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "null")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "default(global::MyApp.MyStruct?)")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum", "default(global::MyApp.MyEnum)")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "null")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "default(global::MyApp.MyEnum?)")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "null")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "default(global::MyApp.MyClass)")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_ExplicitDefaultValue_Warns( string dependencyPropertyType, string propertyType, @@ -1462,6 +1545,10 @@ public partial class MyControl : Control set => SetValue(NameProperty, value); } } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } + public class MyClass { } """; await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); From 3c250dac2e643294b77b71fa053d19d343787acd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Dec 2024 00:03:52 -0800 Subject: [PATCH 077/126] Fix some analyzer bugs, add unit tests --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 8 ++- .../Extensions/ITypeSymbolExtensions.cs | 32 ++++++++++ .../Test_Analyzers.cs | 2 + ...ndencyPropertyOnManualPropertyCodeFixer.cs | 62 +++++++++++++++++++ 4 files changed, 103 insertions(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 037414fe0..2e898dc22 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -438,7 +438,13 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla if (conversionOperation.Operand.Type is { TypeKind: TypeKind.Enum } operandType && operandType.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml))) { - fieldFlags.DefaultValue = null; + // Before actually enabling the optimization, validate that the default value is actually + // the same as the default value of the enum (ie. the value of its first declared field). + if (operandType.TryGetDefaultValueForEnumType(out object? defaultValue) && + conversionOperation.Operand.ConstantValue.Value == defaultValue) + { + fieldFlags.DefaultValue = null; + } } } else diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 261ddd64e..959425807 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics.CodeAnalysis; using CommunityToolkit.GeneratedDependencyProperty.Helpers; using Microsoft.CodeAnalysis; @@ -23,6 +24,37 @@ public static bool IsDefaultValueNull(this ITypeSymbol symbol) return symbol is { IsValueType: false } or INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T }; } + /// + /// Tries to get the default value of a given enum type. + /// + /// The input instance to check. + /// The resulting default value for , if it was an enum type. + /// Whether was retrieved successfully. + public static bool TryGetDefaultValueForEnumType(this ITypeSymbol symbol, [NotNullWhen(true)] out object? value) + { + if (symbol.TypeKind is not TypeKind.Enum) + { + value = default; + + return false; + } + + // The default value of the enum is the value of its first constant field + foreach (ISymbol memberSymbol in symbol.GetMembers()) + { + if (memberSymbol is IFieldSymbol { IsConst: true, ConstantValue: object defaultValue }) + { + value = defaultValue; + + return true; + } + } + + value = default; + + return false; + } + /// /// Checks whether or not a given type symbol has a specified fully qualified metadata name. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 13f7dbbe5..db6972429 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1382,6 +1382,7 @@ public string? Name [TestMethod] [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "global::System.TimeSpan.FromSeconds(1)")] [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "global::System.TimeSpan.FromSeconds(1)")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Collapsed")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_ExplicitDefaultValue_DoesNotWarn( string dependencyPropertyType, string propertyType, @@ -1502,6 +1503,7 @@ public class MyClass { } [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "default(global::Windows.Foundation.Rect)")] [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "default(global::Windows.Foundation.Size)")] [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "default(global::Windows.UI.Xaml.Visibility)")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Visible")] [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "default(System.TimeSpan)")] [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "default(global::System.DateTimeOffset)")] [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "null")] diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 1a8f31def..01cf0971a 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.Foundation; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using CSharpCodeFixTest = CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers.CSharpCodeFixTest< @@ -25,7 +26,41 @@ public class Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer [DataRow("object", "object")] [DataRow("object", "object?")] [DataRow("int", "int")] + [DataRow("byte", "byte")] + [DataRow("sbyte", "sbyte")] + [DataRow("short", "short")] + [DataRow("ushort", "ushort")] + [DataRow("uint", "uint")] + [DataRow("long", "long")] + [DataRow("ulong", "ulong")] + [DataRow("char", "char")] + [DataRow("float", "float")] + [DataRow("double", "double")] + [DataRow("global::System.Numerics.Matrix3x2", "global::System.Numerics.Matrix3x2")] + [DataRow("global::System.Numerics.Matrix4x4", "global::System.Numerics.Matrix4x4")] + [DataRow("global::System.Numerics.Plane", "global::System.Numerics.Plane")] + [DataRow("global::System.Numerics.Quaternion", "global::System.Numerics.Quaternion")] + [DataRow("global::System.Numerics.Vector2", "global::System.Numerics.Vector2")] + [DataRow("global::System.Numerics.Vector3", "global::System.Numerics.Vector3")] + [DataRow("global::System.Numerics.Vector4", "global::System.Numerics.Vector4")] + [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point")] + [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect")] + [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility")] [DataRow("int?", "int?")] + [DataRow("byte?", "byte?")] + [DataRow("char?", "char?")] + [DataRow("long?", "long?")] + [DataRow("float?", "float?")] + [DataRow("double?", "double?")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?")] + [DataRow("global::System.Guid?", "global::System.Guid?")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?")] + [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?")] public async Task SimpleProperty(string dependencyPropertyType, string propertyType) { string original = $$""" @@ -48,6 +83,9 @@ public class MyControl : Control set => SetValue(NameProperty, value); } } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } """; string @fixed = $$""" @@ -62,6 +100,9 @@ public partial class MyControl : Control [GeneratedDependencyProperty] public partial {{propertyType}} {|CS9248:Name|} { get; set; } } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } """; CSharpCodeFixTest test = new(LanguageVersion.Preview) @@ -71,6 +112,7 @@ public partial class MyControl : Control ReferenceAssemblies = ReferenceAssemblies.Net.Net80, TestState = { AdditionalReferences = { + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) @@ -93,6 +135,23 @@ public partial class MyControl : Control [DataRow("int?", "int?", "default(int?)")] [DataRow("int?", "int?", "null")] [DataRow("System.TimeSpan", "System.TimeSpan", "default(System.TimeSpan)")] + [DataRow("global::System.Numerics.Matrix3x2", "global::System.Numerics.Matrix3x2", "default(global::System.Numerics.Matrix3x2)")] + [DataRow("global::System.Numerics.Matrix4x4", "global::System.Numerics.Matrix4x4", "default(global::System.Numerics.Matrix4x4)")] + [DataRow("global::System.Numerics.Plane", "global::System.Numerics.Plane", "default(global::System.Numerics.Plane)")] + [DataRow("global::System.Numerics.Quaternion", "global::System.Numerics.Quaternion", "default(global::System.Numerics.Quaternion)")] + [DataRow("global::System.Numerics.Vector2", "global::System.Numerics.Vector2", "default(global::System.Numerics.Vector2)")] + [DataRow("global::System.Numerics.Vector3", "global::System.Numerics.Vector3", "default(global::System.Numerics.Vector3)")] + [DataRow("global::System.Numerics.Vector4", "global::System.Numerics.Vector4", "default(global::System.Numerics.Vector4)")] + [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "default(global::Windows.Foundation.Point)")] + [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "default(global::Windows.Foundation.Rect)")] + [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "default(global::Windows.Foundation.Size)")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "default(global::Windows.UI.Xaml.Visibility)")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Visible")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "default(global::System.DateTimeOffset?)")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "null")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "null")] + [DataRow("global::System.Guid?", "global::System.Guid?", "null")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "null")] public async Task SimpleProperty_WithExplicitValue_DefaultValue( string dependencyPropertyType, string propertyType, @@ -145,6 +204,7 @@ public partial class MyControl : Control ReferenceAssemblies = ReferenceAssemblies.Net.Net80, TestState = { AdditionalReferences = { + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) @@ -160,6 +220,7 @@ public partial class MyControl : Control [DataRow("int", "int", "42")] [DataRow("int?", "int?", "0")] [DataRow("int?", "int?", "42")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Collapsed")] public async Task SimpleProperty_WithExplicitValue_NotDefault( string dependencyPropertyType, string propertyType, @@ -212,6 +273,7 @@ public partial class MyControl : Control ReferenceAssemblies = ReferenceAssemblies.Net.Net80, TestState = { AdditionalReferences = { + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) From 3241ea549f47952806e5b9d5ea6e24bd5a7bd5cc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Dec 2024 10:08:31 -0800 Subject: [PATCH 078/126] Adjust priority for enum typed constants --- .../Models/TypedConstantInfo.Factory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index e98111cde..e6fcecc9f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -87,6 +87,7 @@ public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out Typed { ({ SpecialType: SpecialType.System_String }, string text) => new Primitive.String(text), ({ SpecialType: SpecialType.System_Boolean}, bool flag) => new Primitive.Boolean(flag), + (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), (_, byte b) => new Primitive.Of(b), (_, char c) => new Primitive.Of(c), (_, double d) => new Primitive.Of(d), @@ -99,7 +100,6 @@ public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out Typed (_, ulong ul) => new Primitive.Of(ul), (_, ushort ush) => new Primitive.Of(ush), (_, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), - (INamedTypeSymbol { TypeKind: TypeKind.Enum}, object value) => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), _ => throw new ArgumentException("Invalid typed constant type"), }; From 7d65ea17110bb265e410ca58916374a49fb2ebe3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Dec 2024 11:53:18 -0800 Subject: [PATCH 079/126] Fix handling of defaulted custom structs in analyzer --- .../DependencyPropertyGenerator.Execute.cs | 59 ++------------- ...endencyPropertyOnManualPropertyAnalyzer.cs | 14 +++- .../Extensions/WinRTExtensions.cs | 71 ++++++++++++++++++ .../Models/DependencyPropertyDefaultValue.cs | 5 ++ .../Models/TypedConstantInfo.cs | 5 ++ .../Test_Analyzers.cs | 32 ++++---- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 74 ++++++++++++++++++- 7 files changed, 189 insertions(+), 71 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index f3dc16bc8..566094d56 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -27,11 +27,6 @@ partial class DependencyPropertyGenerator /// private static partial class Execute { - /// - /// Placeholder for . - /// - private static readonly DependencyPropertyDefaultValue.Null NullInfo = new(); - /// /// Generates the sources for the embedded types, for PrivateAssets="all" scenarios. /// @@ -274,7 +269,7 @@ public static DependencyPropertyDefaultValue GetDefaultValue( } // Invalid callback, the analyzer will emit an error - return NullInfo; + return DependencyPropertyDefaultValue.Null.Instance; } token.ThrowIfCancellationRequested(); @@ -313,7 +308,7 @@ public static DependencyPropertyDefaultValue GetDefaultValue( } // Otherwise, the value has been explicitly set to 'null', so let's respect that - return NullInfo; + return DependencyPropertyDefaultValue.Null.Instance; } token.ThrowIfCancellationRequested(); @@ -322,54 +317,14 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // First we need to special case non nullable values, as for those we need 'default'. if (!propertySymbol.Type.IsDefaultValueNull()) { - string fullyQualifiedTypeName = propertySymbol.Type.GetFullyQualifiedName(); - - // There is a special case for this: if the type of the property is a built-in WinRT - // projected enum type or struct type (ie. some projected value type in general, except - // for 'Nullable' values), then we can just use 'null' and bypass creating the property - // metadata. The WinRT runtime will automatically instantiate a default value for us. - if (propertySymbol.Type.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) || - propertySymbol.Type.IsContainedInNamespace("System.Numerics")) - { - return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); - } - - // Special case a few more well known value types that are mapped for WinRT - if (propertySymbol.Type.Name is "Point" or "Rect" or "Size" && - propertySymbol.Type.IsContainedInNamespace("Windows.Foundation")) - { - return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); - } - - // Special case two more system types - if (propertySymbol.Type is INamedTypeSymbol { MetadataName: "TimeSpan" or "DateTimeOffset", ContainingNamespace.MetadataName: "System" }) - { - return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); - } - - // Lastly, special case the well known primitive types - if (propertySymbol.Type.SpecialType is - SpecialType.System_Int32 or - SpecialType.System_Byte or - SpecialType.System_SByte or - SpecialType.System_Int16 or - SpecialType.System_UInt16 or - SpecialType.System_UInt32 or - SpecialType.System_Int64 or - SpecialType.System_UInt64 or - SpecialType.System_Char or - SpecialType.System_Single or - SpecialType.System_Double) - { - return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true); - } - - // In all other cases, just use 'default(T)' here - return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: false); + // For non nullable types, we return 'default(T)', unless we can optimize for projected types + return new DependencyPropertyDefaultValue.Default( + TypeName: propertySymbol.Type.GetFullyQualifiedName(), + IsProjectedType: propertySymbol.Type.IsWellKnownWinRTProjectedValueType(useWindowsUIXaml)); } // For all other ones, we can just use the 'null' placeholder again - return NullInfo; + return DependencyPropertyDefaultValue.Null.Instance; } /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 2e898dc22..10881e9f5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -65,6 +65,7 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(static context => { // Get the XAML mode to use @@ -393,7 +394,18 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla } // First, check if the metadata is 'null' (simplest case) - if (propertyMetadataArgument.Value.ConstantValue is not { HasValue: true, Value: null }) + if (propertyMetadataArgument.Value.ConstantValue is { HasValue: true, Value: null }) + { + // Here we need to special case non nullable value types that are not well known WinRT projected types. + // In this case, we cannot rely on XAML calling their default constructor. Rather, we need to preserve + // the explicit 'null' value that users had in their code. The analyzer will then warn on these cases + if (!propertyTypeSymbol.IsDefaultValueNull() && + !propertyTypeSymbol.IsWellKnownWinRTProjectedValueType(useWindowsUIXaml)) + { + fieldFlags.DefaultValue = TypedConstantInfo.Null.Instance; + } + } + else { // Next, check if the argument is 'new PropertyMetadata(...)' with the default value for the property type if (propertyMetadataArgument.Value is not IObjectCreationOperation { Arguments: [{ } defaultValueArgument] } objectCreationOperation) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs new file mode 100644 index 000000000..de7983612 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs @@ -0,0 +1,71 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Constants; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for WinRT scenarios. +/// +internal static class WinRTExtensions +{ + /// + /// Checks whether a given type is a well known WinRT projected value type (ie. a type that XAML can default). + /// + /// The input instance to check. + /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. + /// Whether is a well known WinRT projected value type.. + public static bool IsWellKnownWinRTProjectedValueType(this ITypeSymbol symbol, bool useWindowsUIXaml) + { + // This method only cares about non nullable value types + if (symbol.IsDefaultValueNull()) + { + return false; + } + + // There is a special case for this: if the type of the property is a built-in WinRT + // projected enum type or struct type (ie. some projected value type in general, except + // for 'Nullable' values), then we can just use 'null' and bypass creating the property + // metadata. The WinRT runtime will automatically instantiate a default value for us. + if (symbol.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) || + symbol.IsContainedInNamespace("System.Numerics")) + { + return true; + } + + // Special case a few more well known value types that are mapped for WinRT + if (symbol.Name is "Point" or "Rect" or "Size" && + symbol.IsContainedInNamespace("Windows.Foundation")) + { + return true; + } + + // Special case two more system types + if (symbol is INamedTypeSymbol { MetadataName: "TimeSpan" or "DateTimeOffset", ContainingNamespace.MetadataName: "System" }) + { + return true; + } + + // Lastly, special case the well known primitive types + if (symbol.SpecialType is + SpecialType.System_Int32 or + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Char or + SpecialType.System_Single or + SpecialType.System_Double) + { + return true; + } + + return false; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs index 29469028e..993c9d717 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs @@ -16,6 +16,11 @@ internal abstract partial record DependencyPropertyDefaultValue /// public sealed record Null : DependencyPropertyDefaultValue { + /// + /// The shared instance (the type is stateless). + /// + public static Null Instance { get; } = new(); + /// public override string ToString() { diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index 48accd4e1..427f25813 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -23,6 +23,11 @@ internal abstract partial record TypedConstantInfo /// public sealed record Null : TypedConstantInfo { + /// + /// The shared instance (the type is stateless). + /// + public static Null Instance { get; } = new(); + /// public override string ToString() { diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index db6972429..b073ab716 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1382,7 +1382,6 @@ public string? Name [TestMethod] [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "global::System.TimeSpan.FromSeconds(1)")] [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "global::System.TimeSpan.FromSeconds(1)")] - [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Collapsed")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_ExplicitDefaultValue_DoesNotWarn( string dependencyPropertyType, string propertyType, @@ -1425,21 +1424,21 @@ public enum MyEnum { A, B, C } [DataRow("object", "object?")] [DataRow("int", "int")] [DataRow("int?", "int?")] - [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "null")] - [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "default(global::System.TimeSpan?)")] - [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "null")] - [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "default(global::System.DateTimeOffset?)")] - [DataRow("global::System.Guid?", "global::System.Guid?", "default(global::System.Guid?)")] - [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "default(global::System.Collections.Generic.KeyValuePair?)")] - [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?", "null")] - [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct", "default(global::MyApp.MyStruct)")] - [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "null")] - [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?", "default(global::MyApp.MyStruct?)")] - [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum", "default(global::MyApp.MyEnum)")] - [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "null")] - [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?", "default(global::MyApp.MyEnum?)")] - [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "null")] - [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass", "default(global::MyApp.MyClass)")] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan")] + [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?")] + [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset")] + [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?")] + [DataRow("global::System.Guid?", "global::System.Guid?")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?")] + [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?" )] + [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?")] + [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?")] + [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass")] public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_Warns( string dependencyPropertyType, string propertyType) @@ -1504,6 +1503,7 @@ public class MyClass { } [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "default(global::Windows.Foundation.Size)")] [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "default(global::Windows.UI.Xaml.Visibility)")] [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Visible")] + [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Collapsed")] [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "default(System.TimeSpan)")] [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "default(global::System.DateTimeOffset)")] [DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "null")] diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 01cf0971a..5b7badbb1 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -57,10 +57,10 @@ public class Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer [DataRow("global::System.TimeSpan?", "global::System.TimeSpan?")] [DataRow("global::System.Guid?", "global::System.Guid?")] [DataRow("global::System.Collections.Generic.KeyValuePair?", "global::System.Collections.Generic.KeyValuePair?")] - [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct")] [DataRow("global::MyApp.MyStruct?", "global::MyApp.MyStruct?")] - [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum")] [DataRow("global::MyApp.MyEnum?", "global::MyApp.MyEnum?")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass")] + [DataRow("global::MyApp.MyClass", "global::MyApp.MyClass?")] public async Task SimpleProperty(string dependencyPropertyType, string propertyType) { string original = $$""" @@ -86,6 +86,7 @@ public class MyControl : Control public struct MyStruct { public string X { get; set; } } public enum MyEnum { A, B, C } + public class MyClass { } """; string @fixed = $$""" @@ -101,6 +102,75 @@ public partial class MyControl : Control public partial {{propertyType}} {|CS9248:Name|} { get; set; } } + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } + public class MyClass { } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + TestState = { AdditionalReferences = + { + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + }} + }; + + await test.RunAsync(); + } + + // These are custom value types, on properties where the metadata was set to 'null'. In this case, the + // default value would just be 'null', as XAML can't default initialize them. To preserve behavior, + // we must include an explicit default value. This will warn when the code is recompiled, but that + // is expected, because this specific scenario was (1) niche, and (2) kinda busted already anyway. + [TestMethod] + [DataRow("global::MyApp.MyStruct", "global::MyApp.MyStruct")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum")] + public async Task SimpleProperty_ExplicitNull(string dependencyPropertyType, string propertyType) + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: nameof(Name), + propertyType: typeof({{dependencyPropertyType}}), + ownerType: typeof(MyControl), + typeMetadata: null); + + public {{propertyType}} [|Name|] + { + get => ({{propertyType}})GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + + public struct MyStruct { public string X { get; set; } } + public enum MyEnum { A, B, C } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = null)] + public partial {{propertyType}} {|CS9248:Name|} { get; set; } + } + public struct MyStruct { public string X { get; set; } } public enum MyEnum { A, B, C } """; From e48226cb53eba00ce13164b66ea3a1d823d17654 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Dec 2024 15:03:39 -0800 Subject: [PATCH 080/126] Improve formatting for known enum members --- .../Extensions/ITypeSymbolExtensions.cs | 40 ++++++++++++++++++- .../Models/TypedConstantInfo.Factory.cs | 14 +++++-- .../Models/TypedConstantInfo.cs | 14 +++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 959425807..04b23a33a 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -34,7 +34,7 @@ public static bool TryGetDefaultValueForEnumType(this ITypeSymbol symbol, [NotNu { if (symbol.TypeKind is not TypeKind.Enum) { - value = default; + value = null; return false; } @@ -50,7 +50,43 @@ public static bool TryGetDefaultValueForEnumType(this ITypeSymbol symbol, [NotNu } } - value = default; + value = null; + + return false; + } + + /// + /// Tries to get the name of the enum field matching a given value. + /// + /// The input instance to check. + /// The value for to try to get the field for. + /// The name of the field with the specified value, if found. + /// Whether was successfully retrieved. + public static bool TryGetEnumFieldName(this ITypeSymbol symbol, object value, [NotNullWhen(true)] out string? fieldName) + { + if (symbol.TypeKind is not TypeKind.Enum) + { + fieldName = null; + + return false; + } + + // The default value of the enum is the value of its first constant field + foreach (ISymbol memberSymbol in symbol.GetMembers()) + { + if (memberSymbol is not IFieldSymbol { IsConst: true, ConstantValue: object fieldValue } fieldSymbol) + { + continue; + } + + if (fieldValue == value) + { + fieldName = fieldSymbol.Name; + + return true; + } + } + fieldName = null; return false; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index e6fcecc9f..757d43467 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; namespace CommunityToolkit.GeneratedDependencyProperty.Models; @@ -53,8 +54,12 @@ public static TypedConstantInfo Create(TypedConstant arg) ushort ush => new Primitive.Of(ush), _ => throw new ArgumentException("Invalid primitive type") }, - (TypedConstantKind.Type, ITypeSymbol type) => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), - (TypedConstantKind.Enum, object value) => new Enum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), + (TypedConstantKind.Type, ITypeSymbol type) + => new Type(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), + (TypedConstantKind.Enum, object value) when arg.Type!.TryGetEnumFieldName(value, out string? fieldName) + => new KnownEnum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), fieldName), + (TypedConstantKind.Enum, object value) + => new Enum(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), _ => throw new ArgumentException("Invalid typed constant type"), }; } @@ -87,7 +92,10 @@ public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out Typed { ({ SpecialType: SpecialType.System_String }, string text) => new Primitive.String(text), ({ SpecialType: SpecialType.System_Boolean}, bool flag) => new Primitive.Boolean(flag), - (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), + (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) when (operationType.TryGetEnumFieldName(value, out string? fieldName)) + => new KnownEnum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), fieldName), + (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) + => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), (_, byte b) => new Primitive.Of(b), (_, char c) => new Primitive.Of(c), (_, double d) => new Primitive.Of(d), diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index 427f25813..18b781dd4 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -138,6 +138,20 @@ public override string ToString() } } + /// + /// A type representing a known enum value. + /// + /// The enum type name. + /// The enum field name. + public sealed record KnownEnum(string TypeName, string FieldName) : TypedConstantInfo + { + /// + public override string ToString() + { + return $"{TypeName}.{FieldName}"; + } + } + /// /// A type representing an enum value. /// From 4d085d37f58b2406e78278312d3ceeca37764b07 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 16 Dec 2024 07:31:18 -0800 Subject: [PATCH 081/126] Improve codegen for property changed callbacks --- .../DependencyPropertyGenerator.Execute.cs | 123 +- .../Helpers/IndentedTextWriter.cs | 2 +- .../Test_DependencyPropertyGenerator.cs | 1232 ++++++++++++++++- 3 files changed, 1291 insertions(+), 66 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 566094d56..99f7c4270 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -812,6 +812,9 @@ public static bool RequiresAdditionalTypes(EquatableArrayThe instance to write into. public static void WriteAdditionalTypes(EquatableArray propertyInfos, IndentedTextWriter writer) { + string fullyQualifiedTypeName = propertyInfos[0].Hierarchy.GetFullyQualifiedTypeName(); + + // Define the 'PropertyChangedCallbacks' type writer.WriteLine("using global::System.Runtime.CompilerServices;"); writer.WriteLine($"using global::{WellKnownTypeNames.XamlNamespace(propertyInfos[0].UseWindowsUIXaml)};"); writer.WriteLine(); @@ -821,15 +824,39 @@ public static void WriteAdditionalTypes(EquatableArray p /// """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName); - writer.WriteLine("file static class PropertyChangedCallbacks"); + writer.WriteLine("file sealed class PropertyChangedCallbacks"); using (writer.WriteBlock()) { - string fullyQualifiedTypeName = propertyInfos[0].Hierarchy.GetFullyQualifiedTypeName(); + // Shared dummy instance field (to make delegate invocations faster) + writer.WriteLine(""" + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + """, isMultiline: true); + + int numberOfSharedPropertyCallbacks = propertyInfos.Count(static property => !property.IsPropertyChangedCallbackImplemented && property.IsSharedPropertyChangedCallbackImplemented); + bool shouldCacheSharedPropertyChangedCallback = numberOfSharedPropertyCallbacks > 1; + bool shouldGenerateSharedPropertyCallback = numberOfSharedPropertyCallbacks > 0; + + // If the shared callback should be cached, do that here + if (shouldCacheSharedPropertyChangedCallback) + { + writer.WriteLine(); + writer.WriteLine(""" + /// Shared instance, for all properties only using the shared callback. + private static readonly PropertyChangedCallback SharedPropertyChangedCallback = new(Instance.OnPropertyChanged); + """, isMultiline: true); + } // Write the public accessors to use in property initializers - writer.WriteLineSeparatedMembers(propertyInfos.AsSpan(), (propertyInfo, writer) => + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) { + if (!propertyInfo.IsPropertyChangedCallbackImplemented && !propertyInfo.IsSharedPropertyChangedCallbackImplemented) + { + continue; + } + + writer.WriteLine(); writer.WriteLine($$""" /// /// Gets a value for . @@ -837,56 +864,106 @@ public static void WriteAdditionalTypes(EquatableArray p /// The value with the right callbacks. public static PropertyChangedCallback {{propertyInfo.PropertyName}}() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - {{fullyQualifiedTypeName}} __this = ({{fullyQualifiedTypeName}})d; - """, isMultiline: true); writer.IncreaseIndent(); - writer.IncreaseIndent(); - // Per-property callback, if present + // There are 3 possible scenarios to handle: + // 1) The property uses a dedicated property changed callback. In this case we always need a dedicated stub. + // 2) The property uses the shared callback only, and there's more than one property like this. Reuse the instance. + // 3) This is the only property using the shared callback only. In that case, create a new delegate over it. if (propertyInfo.IsPropertyChangedCallbackImplemented) { - writer.WriteLine($"On{propertyInfo.PropertyName}PropertyChanged(__this, e);"); + writer.WriteLine($"return new(Instance.On{propertyInfo.PropertyName}PropertyChanged);"); + } + else if (shouldCacheSharedPropertyChangedCallback) + { + writer.WriteLine("return SharedPropertyChangedCallback;"); + } + else + { + writer.WriteLine("return new(Instance.OnPropertyChanged);"); + } + + writer.DecreaseIndent(); + writer.WriteLine("}"); + } + + // Write the private combined + foreach (DependencyPropertyInfo propertyInfo in propertyInfos) + { + if (!propertyInfo.IsPropertyChangedCallbackImplemented) + { + continue; } - // Shared callback, if present + writer.WriteLine(); + writer.WriteLine($$""" + /// + private void On{{propertyInfo.PropertyName}}PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + {{fullyQualifiedTypeName}} __this = ({{fullyQualifiedTypeName}})d; + + PropertyChangedUnsafeAccessors.On{{propertyInfo.PropertyName}}PropertyChanged(__this, e); + """, isMultiline: true); + + // Shared callback, if needed if (propertyInfo.IsSharedPropertyChangedCallbackImplemented) { - writer.WriteLine("OnPropertyChanged(__this, e);"); + writer.IncreaseIndent(); + writer.WriteLine($"PropertyChangedUnsafeAccessors.On{propertyInfo.PropertyName}PropertyChanged(__this, e);"); + writer.DecreaseIndent(); } - // Close the method and return the 'Invoke' method as a delegate (just one allocation here) - writer.DecreaseIndent(); - writer.DecreaseIndent(); - writer.WriteLine(""" - } + writer.WriteLine("}"); + } - return new(Invoke); + // If we need to generate the shared callback, let's also generate its target method + if (shouldGenerateSharedPropertyCallback) + { + writer.WriteLine(); + writer.WriteLine($$""" + /// + private void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + {{fullyQualifiedTypeName}} __this = ({{fullyQualifiedTypeName}})d; + + PropertyChangedUnsafeAccessors.OnPropertyChanged(__this, e); } """, isMultiline: true); - }); + } + } + + // Define the 'PropertyChangedAccessors' type + writer.WriteLine(); + writer.WriteLine($""" + /// + /// Contains all unsafe accessors for . + /// + """, isMultiline: true); + writer.WriteGeneratedAttributes(GeneratorName); + writer.WriteLine("file sealed class PropertyChangedUnsafeAccessors"); + using (writer.WriteBlock()) + { // Write the accessors for all WinRT-based callbacks (not the shared one) foreach (DependencyPropertyInfo propertyInfo in propertyInfos.Where(static property => property.IsPropertyChangedCallbackImplemented)) { - writer.WriteLine(); + writer.WriteLine(skipIfPresent: true); writer.WriteLine($""" /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "On{propertyInfo.PropertyName}PropertyChanged")] - private static extern void On{propertyInfo.PropertyName}PropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); + public static extern void On{propertyInfo.PropertyName}PropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); """, isMultiline: true); } // Also emit one for the shared callback, if it's ever used if (propertyInfos.Any(static property => property.IsSharedPropertyChangedCallbackImplemented)) { - writer.WriteLine(); + writer.WriteLine(skipIfPresent: true); writer.WriteLine($""" /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] - private static extern void OnPropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); + public static extern void OnPropertyChanged({fullyQualifiedTypeName} _, DependencyPropertyChangedEventArgs e); """, isMultiline: true); } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs index 0883e84d1..b244356d6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Helpers/IndentedTextWriter.cs @@ -227,7 +227,7 @@ public void WriteIf(bool condition, [InterpolatedStringHandlerArgument("", nameo /// Indicates whether to skip adding the line if there already is one. public void WriteLine(bool skipIfPresent = false) { - if (skipIfPresent && this.builder.WrittenSpan is [.., '\n', '\n']) + if (skipIfPresent && this.builder.WrittenSpan is [.., '\n' or '{', '\n']) { return; } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 5d01fd0b8..a84b3f2d0 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -276,27 +276,40 @@ namespace CommunityToolkit.WinUI.DependencyPropertyGenerator [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - file static class PropertyChangedCallbacks + file sealed class PropertyChangedCallbacks { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + /// /// Gets a value for . /// /// The value with the right callbacks. public static PropertyChangedCallback Number() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + return new(Instance.OnNumberPropertyChanged); + } - OnNumberPropertyChanged(__this, e); - } + /// + private void OnNumberPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; - return new(Invoke); + PropertyChangedUnsafeAccessors.OnNumberPropertyChanged(__this, e); } + } + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] - private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); } } """; @@ -791,27 +804,40 @@ namespace CommunityToolkit.WinUI.DependencyPropertyGenerator [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - file static class PropertyChangedCallbacks + file sealed class PropertyChangedCallbacks { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + /// /// Gets a value for . /// /// The value with the right callbacks. public static PropertyChangedCallback Number() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + return new(Instance.OnNumberPropertyChanged); + } - OnNumberPropertyChanged(__this, e); - } + /// + private void OnNumberPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; - return new(Invoke); + PropertyChangedUnsafeAccessors.OnNumberPropertyChanged(__this, e); } + } + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] - private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); } } """; @@ -1064,27 +1090,40 @@ namespace CommunityToolkit.WinUI.DependencyPropertyGenerator [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - file static class PropertyChangedCallbacks + file sealed class PropertyChangedCallbacks { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + /// /// Gets a value for . /// /// The value with the right callbacks. public static PropertyChangedCallback Number() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + return new(Instance.OnNumberPropertyChanged); + } - OnNumberPropertyChanged(__this, e); - } + /// + private void OnNumberPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; - return new(Invoke); + PropertyChangedUnsafeAccessors.OnNumberPropertyChanged(__this, e); } + } + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] - private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); } } """; @@ -1221,27 +1260,40 @@ namespace CommunityToolkit.WinUI.DependencyPropertyGenerator [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - file static class PropertyChangedCallbacks + file sealed class PropertyChangedCallbacks { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + /// /// Gets a value for . /// /// The value with the right callbacks. public static PropertyChangedCallback Number() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + return new(Instance.OnPropertyChanged); + } - OnPropertyChanged(__this, e); - } + /// + private void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; - return new(Invoke); + PropertyChangedUnsafeAccessors.OnPropertyChanged(__this, e); } + } + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] - private static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); } } """; @@ -1382,32 +1434,45 @@ namespace CommunityToolkit.WinUI.DependencyPropertyGenerator [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - file static class PropertyChangedCallbacks + file sealed class PropertyChangedCallbacks { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + /// /// Gets a value for . /// /// The value with the right callbacks. public static PropertyChangedCallback Number() { - static void Invoke(object d, DependencyPropertyChangedEventArgs e) - { - global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + return new(Instance.OnNumberPropertyChanged); + } - OnNumberPropertyChanged(__this, e); - OnPropertyChanged(__this, e); - } + /// + private void OnNumberPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; - return new(Invoke); + PropertyChangedUnsafeAccessors.OnNumberPropertyChanged(__this, e); + PropertyChangedUnsafeAccessors.OnNumberPropertyChanged(__this, e); } + } + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnNumberPropertyChanged")] - private static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnNumberPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); /// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] - private static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + public static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); } } """; @@ -2330,6 +2395,1089 @@ public partial string? LastName CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); } + [TestMethod] + public void MultipleProperties_WithNoCaching_WithJustOnePropertyCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? FirstName { get; set; } + + [GeneratedDependencyProperty] + public partial string? LastName { get; set; } + + partial void OnFirstNamePropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty FirstNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "FirstName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.FirstName())); + + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty LastNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "LastName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? FirstName + { + get + { + object? __boxedValue = GetValue(FirstNameProperty); + + OnFirstNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnFirstNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnFirstNameSet(ref value); + + object? __boxedValue = value; + + OnFirstNameSet(ref __boxedValue); + + SetValue(FirstNameProperty, __boxedValue); + + OnFirstNameChanged(value); + } + } + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? LastName + { + get + { + object? __boxedValue = GetValue(LastNameProperty); + + OnLastNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnLastNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnLastNameSet(ref value); + + object? __boxedValue = value; + + OnLastNameSet(ref __boxedValue); + + SetValue(LastNameProperty, __boxedValue); + + OnLastNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedCallbacks + { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback FirstName() + { + return new(Instance.OnFirstNamePropertyChanged); + } + + /// + private void OnFirstNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnFirstNamePropertyChanged(__this, e); + } + } + + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnFirstNamePropertyChanged")] + public static extern void OnFirstNamePropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void MultipleProperties_WithNoCaching_WithSharedPropertyCallback() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? FirstName { get; set; } + + [GeneratedDependencyProperty] + public partial string? LastName { get; set; } + + partial void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty FirstNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "FirstName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.FirstName())); + + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty LastNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "LastName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.LastName())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? FirstName + { + get + { + object? __boxedValue = GetValue(FirstNameProperty); + + OnFirstNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnFirstNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnFirstNameSet(ref value); + + object? __boxedValue = value; + + OnFirstNameSet(ref __boxedValue); + + SetValue(FirstNameProperty, __boxedValue); + + OnFirstNameChanged(value); + } + } + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? LastName + { + get + { + object? __boxedValue = GetValue(LastNameProperty); + + OnLastNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnLastNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnLastNameSet(ref value); + + object? __boxedValue = value; + + OnLastNameSet(ref __boxedValue); + + SetValue(LastNameProperty, __boxedValue); + + OnLastNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedCallbacks + { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + + /// Shared instance, for all properties only using the shared callback. + private static readonly PropertyChangedCallback SharedPropertyChangedCallback = new(Instance.OnPropertyChanged); + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback FirstName() + { + return SharedPropertyChangedCallback; + } + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback LastName() + { + return SharedPropertyChangedCallback; + } + + /// + private void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnPropertyChanged(__this, e); + } + } + + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + public static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void MultipleProperties_WithNoCaching_WithMixedPropertyCallbacks() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? FirstName { get; set; } + + [GeneratedDependencyProperty] + public partial string? LastName { get; set; } + + partial void OnFirstNamePropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + + partial void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty FirstNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "FirstName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.FirstName())); + + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty LastNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "LastName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.LastName())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? FirstName + { + get + { + object? __boxedValue = GetValue(FirstNameProperty); + + OnFirstNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnFirstNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnFirstNameSet(ref value); + + object? __boxedValue = value; + + OnFirstNameSet(ref __boxedValue); + + SetValue(FirstNameProperty, __boxedValue); + + OnFirstNameChanged(value); + } + } + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? LastName + { + get + { + object? __boxedValue = GetValue(LastNameProperty); + + OnLastNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnLastNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnLastNameSet(ref value); + + object? __boxedValue = value; + + OnLastNameSet(ref __boxedValue); + + SetValue(LastNameProperty, __boxedValue); + + OnLastNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedCallbacks + { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback FirstName() + { + return new(Instance.OnFirstNamePropertyChanged); + } + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback LastName() + { + return new(Instance.OnPropertyChanged); + } + + /// + private void OnFirstNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnFirstNamePropertyChanged(__this, e); + PropertyChangedUnsafeAccessors.OnFirstNamePropertyChanged(__this, e); + } + + /// + private void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnPropertyChanged(__this, e); + } + } + + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnFirstNamePropertyChanged")] + public static extern void OnFirstNamePropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + public static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + + [TestMethod] + public void MultipleProperties_WithNoCaching_WithMixedPropertyCallbacks2() + { + const string source = """ + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? FirstName { get; set; } + + [GeneratedDependencyProperty] + public partial string? LastName { get; set; } + + partial void OnFirstNamePropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + + partial void OnLastNamePropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + + partial void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty FirstNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "FirstName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.FirstName())); + + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty LastNameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "LastName", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: new global::Windows.UI.Xaml.PropertyMetadata( + defaultValue: null, + propertyChangedCallback: global::CommunityToolkit.WinUI.DependencyPropertyGenerator.PropertyChangedCallbacks.LastName())); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? FirstName + { + get + { + object? __boxedValue = GetValue(FirstNameProperty); + + OnFirstNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnFirstNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnFirstNameSet(ref value); + + object? __boxedValue = value; + + OnFirstNameSet(ref __boxedValue); + + SetValue(FirstNameProperty, __boxedValue); + + OnFirstNameChanged(value); + } + } + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? LastName + { + get + { + object? __boxedValue = GetValue(LastNameProperty); + + OnLastNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnLastNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnLastNameSet(ref value); + + object? __boxedValue = value; + + OnLastNameSet(ref __boxedValue); + + SetValue(LastNameProperty, __boxedValue); + + OnLastNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnFirstNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnLastNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + + namespace CommunityToolkit.WinUI.DependencyPropertyGenerator + { + using global::System.Runtime.CompilerServices; + using global::Windows.UI.Xaml; + + /// + /// Contains shared property changed callbacks for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedCallbacks + { + /// Shared instance, used to speedup delegate invocations (avoids the shuffle thunks). + private static readonly PropertyChangedCallbacks Instance = new(); + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback FirstName() + { + return new(Instance.OnFirstNamePropertyChanged); + } + + /// + /// Gets a value for . + /// + /// The value with the right callbacks. + public static PropertyChangedCallback LastName() + { + return new(Instance.OnLastNamePropertyChanged); + } + + /// + private void OnFirstNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnFirstNamePropertyChanged(__this, e); + PropertyChangedUnsafeAccessors.OnFirstNamePropertyChanged(__this, e); + } + + /// + private void OnLastNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + global::MyNamespace.MyControl __this = (global::MyNamespace.MyControl)d; + + PropertyChangedUnsafeAccessors.OnLastNamePropertyChanged(__this, e); + PropertyChangedUnsafeAccessors.OnLastNamePropertyChanged(__this, e); + } + } + + /// + /// Contains all unsafe accessors for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + file sealed class PropertyChangedUnsafeAccessors + { + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnFirstNamePropertyChanged")] + public static extern void OnFirstNamePropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnLastNamePropertyChanged")] + public static extern void OnLastNamePropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + + /// + [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnPropertyChanged")] + public static extern void OnPropertyChanged(global::MyNamespace.MyControl _, DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + [TestMethod] [DataRow("int")] [DataRow("object")] From bea72f9aef917854e1ababc434c914483b84c1c2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 18 Dec 2024 12:02:12 -0800 Subject: [PATCH 082/126] Add incrementality tests --- .../CSharpGeneratorTest{TGenerator}.cs | 8 +- ...endencyPropertyGenerator_Incrementality.cs | 138 ++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs index 027198c79..0240cf3d0 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -55,7 +55,7 @@ public static void VerifyDiagnostics(string source, params string[] diagnosticsI /// The input source to process. /// The expected source to be generated. /// The language version to use to run the test. - public static void VerifySources(string source, (string Filename, string Source) result, LanguageVersion languageVersion = LanguageVersion.CSharp12) + public static void VerifySources(string source, (string Filename, string Source) result, LanguageVersion languageVersion = LanguageVersion.CSharp13) { RunGenerator(source, out Compilation compilation, out ImmutableArray diagnostics, languageVersion); @@ -89,7 +89,7 @@ public static void VerifyIncrementalSteps( IncrementalStepRunReason outputReason, IncrementalStepRunReason? diagnosticsSourceReason, IncrementalStepRunReason sourceReason, - LanguageVersion languageVersion = LanguageVersion.CSharp12) + LanguageVersion languageVersion = LanguageVersion.CSharp13) { Compilation compilation = CreateCompilation(source, languageVersion); @@ -173,7 +173,7 @@ public static void VerifyIncrementalSteps( /// The input source to process. /// The language version to use to run the test. /// The resulting object. - private static CSharpCompilation CreateCompilation(string source, LanguageVersion languageVersion = LanguageVersion.CSharp12) + private static CSharpCompilation CreateCompilation(string source, LanguageVersion languageVersion = LanguageVersion.CSharp13) { // Get all assembly references for the .NET TFM and ComputeSharp IEnumerable metadataReferences = @@ -209,7 +209,7 @@ private static void RunGenerator( string source, out Compilation compilation, out ImmutableArray diagnostics, - LanguageVersion languageVersion = LanguageVersion.CSharp12) + LanguageVersion languageVersion = LanguageVersion.CSharp13) { Compilation originalCompilation = CreateCompilation(source, languageVersion); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs new file mode 100644 index 000000000..fbb1fa6c8 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs @@ -0,0 +1,138 @@ +// 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 CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +[TestClass] +public class Test_DependencyPropertyGenerator_Incrementality +{ + [TestMethod] + public void ModifiedOptions_ModifiesOutput() + { + const string source = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """"; + + const string updatedSource = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty(DefaultValue = 42)] + public partial int Number { get; set; } + } + """"; + + CSharpGeneratorTest.VerifyIncrementalSteps( + source, + updatedSource, + executeReason: IncrementalStepRunReason.Modified, + diagnosticsReason: null, + outputReason: IncrementalStepRunReason.Modified, + diagnosticsSourceReason: null, + sourceReason: IncrementalStepRunReason.Modified); + } + + [TestMethod] + public void AddedLeadingTrivia_DoesNotModifyOutput() + { + const string source = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """"; + + const string updatedSource = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + /// + /// This is some property. + /// + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """"; + + CSharpGeneratorTest.VerifyIncrementalSteps( + source, + updatedSource, + executeReason: IncrementalStepRunReason.Unchanged, + diagnosticsReason: null, + outputReason: IncrementalStepRunReason.Cached, + diagnosticsSourceReason: null, + sourceReason: IncrementalStepRunReason.Cached); + } + + [TestMethod] + public void AddedOtherMember_DoesNotModifyOutput() + { + const string source = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """"; + + const string updatedSource = """" + using Windows.UI.Xaml; + using CommunityToolkit.WinUI; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + public void Foo() + { + } + + [GeneratedDependencyProperty] + public partial int Number { get; set; } + } + """"; + + CSharpGeneratorTest.VerifyIncrementalSteps( + source, + updatedSource, + executeReason: IncrementalStepRunReason.Unchanged, + diagnosticsReason: null, + outputReason: IncrementalStepRunReason.Cached, + diagnosticsSourceReason: null, + sourceReason: IncrementalStepRunReason.Cached); + } +} From 0262e7836e082c94016f1245560cd72c16ee34cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 18 Dec 2024 12:05:01 -0800 Subject: [PATCH 083/126] Remove unnecessary test code --- .../Constants/WellKnownTrackingNames.cs | 5 -- .../CSharpGeneratorTest{TGenerator}.cs | 64 +++---------------- ...endencyPropertyGenerator_Incrementality.cs | 6 -- 3 files changed, 8 insertions(+), 67 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs index 1539c7ea0..36502c28f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTrackingNames.cs @@ -14,11 +14,6 @@ internal static class WellKnownTrackingNames /// public const string Execute = nameof(Execute); - /// - /// The filtered transform with just output diagnostics. - /// - public const string Diagnostics = nameof(Diagnostics); - /// /// The filtered transform with just output sources. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs index 0240cf3d0..ca28be27b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -76,18 +76,14 @@ public static void VerifySources(string source, (string Filename, string Source) /// The input source to process. /// The updated source to process. /// The reason for the first "Execute" step. - /// The reason for the "Diagnostics" step. /// The reason for the "Output" step. - /// The reason for the output step for the diagnostics. /// The reason for the final output source. /// The language version to use to run the test. public static void VerifyIncrementalSteps( string source, string updatedSource, IncrementalStepRunReason executeReason, - IncrementalStepRunReason? diagnosticsReason, IncrementalStepRunReason outputReason, - IncrementalStepRunReason? diagnosticsSourceReason, IncrementalStepRunReason sourceReason, LanguageVersion languageVersion = LanguageVersion.CSharp13) { @@ -110,61 +106,17 @@ public static void VerifyIncrementalSteps( GeneratorRunResult result = driver.GetRunResult().Results.Single(); - // Get the generated sources and validate them. We have two possible cases: if no diagnostics - // are produced, then just the output source node is triggered. Otherwise, we'll also have one - // output node which is used to emit the gathered diagnostics from the initial transform step. - if (diagnosticsSourceReason is not null) - { - Assert.AreEqual( - expected: 2, - actual: - result.TrackedOutputSteps - .SelectMany(outputStep => outputStep.Value) - .SelectMany(output => output.Outputs) - .Count()); - - // The "Diagnostics" name has one more parent compared to "Output", because it also - // has one extra Where(...) call on the node (used to filter out empty diagnostics). - Assert.AreEqual( - expected: diagnosticsSourceReason, - actual: - result.TrackedOutputSteps - .Single().Value - .Single(run => run.Inputs[0].Source.Inputs[0].Source.Name == "Diagnostics") - .Outputs.Single().Reason); - - Assert.AreEqual( - expected: sourceReason, - actual: - result.TrackedOutputSteps - .Single().Value - .Single(run => run.Inputs[0].Source.Name == "Output") - .Outputs.Single().Reason); - } - else - { - (object Value, IncrementalStepRunReason Reason)[] sourceOuputs = - result.TrackedOutputSteps - .SelectMany(outputStep => outputStep.Value) - .SelectMany(output => output.Outputs) - .ToArray(); - - Assert.AreEqual(1, sourceOuputs.Length); - Assert.AreEqual(sourceReason, sourceOuputs[0].Reason); - } + // Get the generated sources and validate them + (object Value, IncrementalStepRunReason Reason)[] sourceOuputs = + result.TrackedOutputSteps + .SelectMany(outputStep => outputStep.Value) + .SelectMany(output => output.Outputs) + .ToArray(); + Assert.AreEqual(1, sourceOuputs.Length); + Assert.AreEqual(sourceReason, sourceOuputs[0].Reason); Assert.AreEqual(executeReason, result.TrackedSteps["Execute"].Single().Outputs[0].Reason); Assert.AreEqual(outputReason, result.TrackedSteps["Output"].Single().Outputs[0].Reason); - - // Check the diagnostics reason, which might not be present - if (diagnosticsReason is not null) - { - Assert.AreEqual(diagnosticsReason, result.TrackedSteps["Diagnostics"].Single().Outputs[0].Reason); - } - else - { - Assert.IsFalse(result.TrackedSteps.ContainsKey("Diagnostics")); - } } /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs index fbb1fa6c8..eebe7591c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator_Incrementality.cs @@ -45,9 +45,7 @@ public partial class MyControl : DependencyObject source, updatedSource, executeReason: IncrementalStepRunReason.Modified, - diagnosticsReason: null, outputReason: IncrementalStepRunReason.Modified, - diagnosticsSourceReason: null, sourceReason: IncrementalStepRunReason.Modified); } @@ -87,9 +85,7 @@ public partial class MyControl : DependencyObject source, updatedSource, executeReason: IncrementalStepRunReason.Unchanged, - diagnosticsReason: null, outputReason: IncrementalStepRunReason.Cached, - diagnosticsSourceReason: null, sourceReason: IncrementalStepRunReason.Cached); } @@ -130,9 +126,7 @@ public void Foo() source, updatedSource, executeReason: IncrementalStepRunReason.Unchanged, - diagnosticsReason: null, outputReason: IncrementalStepRunReason.Cached, - diagnosticsSourceReason: null, sourceReason: IncrementalStepRunReason.Cached); } } From c315a6c597dd639852821eb1d75ed58dbeec8086 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 20 Dec 2024 12:26:38 +0100 Subject: [PATCH 084/126] Fix some leftovers --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index c7dbf7ec7..2898162fb 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -89,17 +89,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) /// /// The original document being fixed. /// The instance for the current compilation. - /// The resulting attribute list, if successfully retrieved. - /// Whether could be retrieved successfully. - private static bool TryGetGeneratedObservablePropertyAttributeList( + /// The resulting attribute list, if successfully retrieved. + /// Whether could be retrieved successfully. + private static bool TryGetGeneratedDependencyPropertyAttributeList( Document document, SemanticModel semanticModel, - [NotNullWhen(true)] out AttributeListSyntax? observablePropertyAttributeList) + [NotNullWhen(true)] out AttributeListSyntax? generatedDependencyPropertyAttributeList) { // Make sure we can resolve the '[GeneratedDependencyProperty]' attribute if (semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute) is not INamedTypeSymbol attributeSymbol) { - observablePropertyAttributeList = null; + generatedDependencyPropertyAttributeList = null; return false; } @@ -109,7 +109,7 @@ private static bool TryGetGeneratedObservablePropertyAttributeList( // Create the attribute syntax for the new '[GeneratedDependencyProperty]' attribute here too SyntaxNode attributeTypeSyntax = syntaxGenerator.TypeExpression(attributeSymbol).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation); - observablePropertyAttributeList = (AttributeListSyntax)syntaxGenerator.Attribute(attributeTypeSyntax); + generatedDependencyPropertyAttributeList = (AttributeListSyntax)syntaxGenerator.Attribute(attributeTypeSyntax); return true; } @@ -135,7 +135,7 @@ private static async Task ConvertToPartialProperty( await Task.CompletedTask; // If we can't generate the new attribute list, bail (this should never happen) - if (!TryGetGeneratedObservablePropertyAttributeList(document, semanticModel, out AttributeListSyntax? observablePropertyAttributeList)) + if (!TryGetGeneratedDependencyPropertyAttributeList(document, semanticModel, out AttributeListSyntax? generatedDependencyPropertyAttributeList)) { return document; } @@ -146,7 +146,7 @@ private static async Task ConvertToPartialProperty( ConvertToPartialProperty( propertyDeclaration, fieldDeclaration, - observablePropertyAttributeList, + generatedDependencyPropertyAttributeList, syntaxEditor, defaultValueExpression); @@ -159,14 +159,14 @@ private static async Task ConvertToPartialProperty( /// /// The for the property being updated. /// The for the declared property to remove. - /// The with the attribute to add. + /// The with the attribute to add. /// The instance to use. /// The expression for the default value of the property, if present /// An updated document with the applied code fix, and being replaced with a partial property. private static void ConvertToPartialProperty( PropertyDeclarationSyntax propertyDeclaration, FieldDeclarationSyntax fieldDeclaration, - AttributeListSyntax observablePropertyAttributeList, + AttributeListSyntax generatedDependencyPropertyAttributeList, SyntaxEditor syntaxEditor, string? defaultValueExpression) { @@ -175,9 +175,9 @@ private static void ConvertToPartialProperty( // It's important to reuse it, as it has the "add usings" annotation. if (defaultValueExpression is not null) { - observablePropertyAttributeList = + generatedDependencyPropertyAttributeList = AttributeList(SingletonSeparatedList( - observablePropertyAttributeList.Attributes[0] + generatedDependencyPropertyAttributeList.Attributes[0] .AddArgumentListArguments( AttributeArgument(ParseExpression(defaultValueExpression)) .WithNameEquals(NameEquals(IdentifierName("DefaultValue")))))); @@ -194,18 +194,18 @@ private static void ConvertToPartialProperty( newNode: firstAttributeListSyntax.WithoutTrivia()); // If the property has at least an attribute list, move the trivia from it to the new attribute - observablePropertyAttributeList = observablePropertyAttributeList.WithTriviaFrom(firstAttributeListSyntax); + generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(firstAttributeListSyntax); // Insert the new attribute - attributeLists = attributeLists.Insert(0, observablePropertyAttributeList); + attributeLists = attributeLists.Insert(0, generatedDependencyPropertyAttributeList); } else { // Otherwise (there are no attribute lists), transfer the trivia to the new (only) attribute list - observablePropertyAttributeList = observablePropertyAttributeList.WithTriviaFrom(propertyDeclaration); + generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(propertyDeclaration); // Save the new attribute list - attributeLists = attributeLists.Add(observablePropertyAttributeList); + attributeLists = attributeLists.Add(generatedDependencyPropertyAttributeList); } // Get a new property that is partial and with semicolon token accessors @@ -268,7 +268,7 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider } // If we can't generate the new attribute list, bail (this should never happen) - if (!TryGetGeneratedObservablePropertyAttributeList(document, semanticModel, out AttributeListSyntax? observablePropertyAttributeList)) + if (!TryGetGeneratedDependencyPropertyAttributeList(document, semanticModel, out AttributeListSyntax? generatedDependencyPropertyAttributeList)) { return document; } @@ -297,7 +297,7 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider ConvertToPartialProperty( propertyDeclaration, fieldDeclaration, - observablePropertyAttributeList, + generatedDependencyPropertyAttributeList, syntaxEditor, defaultValue); } From 48734058e84c40d76015143e7957f6b6bc5a826b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 20 Dec 2024 12:50:13 +0100 Subject: [PATCH 085/126] Remove unnecessary parentheses --- .../Models/TypedConstantInfo.Factory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index 757d43467..8c49ad307 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -92,7 +92,7 @@ public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out Typed { ({ SpecialType: SpecialType.System_String }, string text) => new Primitive.String(text), ({ SpecialType: SpecialType.System_Boolean}, bool flag) => new Primitive.Boolean(flag), - (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) when (operationType.TryGetEnumFieldName(value, out string? fieldName)) + (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) when operationType.TryGetEnumFieldName(value, out string? fieldName) => new KnownEnum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), fieldName), (INamedTypeSymbol { TypeKind: TypeKind.Enum }, object value) => new Enum(operationType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), value), From fc423c5bdb14acc07e68ebfd2e9ce3790c7a67d6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 20 Dec 2024 13:52:09 +0100 Subject: [PATCH 086/126] Improve code fixer for known enum members --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 91 ++++++++++++++++--- .../Extensions/ITypeSymbolExtensions.cs | 2 +- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 69 +++++++++++++- 3 files changed, 148 insertions(+), 14 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 2898162fb..9004cfa8d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -114,6 +114,71 @@ private static bool TryGetGeneratedDependencyPropertyAttributeList( return true; } + /// + /// Updates an for the [GeneratedDependencyProperty] attribute with the right default value. + /// + /// The original document being fixed. + /// The instance for the current compilation. + /// The expression for the default value of the property, if present + /// The updated attribute syntax. + private static AttributeListSyntax UpdateGeneratedDependencyPropertyAttributeList( + Document document, + SemanticModel semanticModel, + AttributeListSyntax generatedDependencyPropertyAttributeList, + string? defaultValueExpression) + { + // If we do have a default value expression, set it in the attribute. + // We extract the generated attribute so we can add the new argument. + // It's important to reuse it, as it has the "add usings" annotation. + if (defaultValueExpression is not null) + { + ExpressionSyntax parsedExpression = ParseExpression(defaultValueExpression); + + // Special case values which are simple enum member accesses, like 'global::Windows.UI.Xaml.Visibility.Collapsed' + if (parsedExpression is MemberAccessExpressionSyntax { Expression: { } expressionSyntax, Name: IdentifierNameSyntax { Identifier.Text: { } memberName } }) + { + string fullyQualifiedTypeName = expressionSyntax.ToFullString(); + + // Ensure we strip the global prefix, if present (it should always be present) + if (fullyQualifiedTypeName.StartsWith("global::")) + { + fullyQualifiedTypeName = fullyQualifiedTypeName["global::".Length..]; + } + + // Try to resolve the attribute type, if present. This API takes a fully qualified metadata name, not + // a fully qualified type name. However, for virtually all cases for enum types, the two should match. + // That is, they will be the same if the type is not nested, and not generic, which is what we expect. + if (semanticModel.Compilation.GetTypeByMetadataName(fullyQualifiedTypeName) is INamedTypeSymbol enumTypeSymbol) + { + SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(document); + + // Create the identifier syntax for the enum type, with the right annotations + SyntaxNode enumTypeSyntax = syntaxGenerator.TypeExpression(enumTypeSymbol).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation); + + // Create the member access expression for the target enum type + SyntaxNode enumMemberAccessExpressionSyntax = syntaxGenerator.MemberAccessExpression(enumTypeSyntax, memberName); + + // Create the attribute argument to insert + SyntaxNode attributeArgumentSyntax = syntaxGenerator.AttributeArgument("DefaultValue", enumMemberAccessExpressionSyntax); + + // Actually add the argument to the existing attribute syntax + return (AttributeListSyntax)syntaxGenerator.AddAttributeArguments(generatedDependencyPropertyAttributeList, [attributeArgumentSyntax]); + } + } + + // Otherwise, just add the new default value normally + return + AttributeList(SingletonSeparatedList( + generatedDependencyPropertyAttributeList.Attributes[0] + .AddArgumentListArguments( + AttributeArgument(ParseExpression(defaultValueExpression)) + .WithNameEquals(NameEquals(IdentifierName("DefaultValue")))))); + } + + // If we have no value expression, we can just reuse the attribute with no changes + return generatedDependencyPropertyAttributeList; + } + /// /// Applies the code fix to a target identifier and returns an updated document. /// @@ -144,6 +209,8 @@ private static async Task ConvertToPartialProperty( SyntaxEditor syntaxEditor = new(root, document.Project.Solution.Workspace.Services); ConvertToPartialProperty( + document, + semanticModel, propertyDeclaration, fieldDeclaration, generatedDependencyPropertyAttributeList, @@ -157,6 +224,8 @@ private static async Task ConvertToPartialProperty( /// /// Applies the code fix to a target identifier and returns an updated document. /// + /// The original document being fixed. + /// The instance for the current compilation. /// The for the property being updated. /// The for the declared property to remove. /// The with the attribute to add. @@ -164,24 +233,20 @@ private static async Task ConvertToPartialProperty( /// The expression for the default value of the property, if present /// An updated document with the applied code fix, and being replaced with a partial property. private static void ConvertToPartialProperty( + Document document, + SemanticModel semanticModel, PropertyDeclarationSyntax propertyDeclaration, FieldDeclarationSyntax fieldDeclaration, AttributeListSyntax generatedDependencyPropertyAttributeList, SyntaxEditor syntaxEditor, string? defaultValueExpression) { - // If we do have a default value expression, set it in the attribute. - // We extract the generated attribute so we can add the new argument. - // It's important to reuse it, as it has the "add usings" annotation. - if (defaultValueExpression is not null) - { - generatedDependencyPropertyAttributeList = - AttributeList(SingletonSeparatedList( - generatedDependencyPropertyAttributeList.Attributes[0] - .AddArgumentListArguments( - AttributeArgument(ParseExpression(defaultValueExpression)) - .WithNameEquals(NameEquals(IdentifierName("DefaultValue")))))); - } + // Update the attribute to insert with the default value, if present + generatedDependencyPropertyAttributeList = UpdateGeneratedDependencyPropertyAttributeList( + document, + semanticModel, + generatedDependencyPropertyAttributeList, + defaultValueExpression); // Start setting up the updated attribute lists SyntaxList attributeLists = propertyDeclaration.AttributeLists; @@ -295,6 +360,8 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider string? defaultValue = diagnostic.Properties[UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.DefaultValuePropertyName]; ConvertToPartialProperty( + document, + semanticModel, propertyDeclaration, fieldDeclaration, generatedDependencyPropertyAttributeList, diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 04b23a33a..c7306082b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -79,7 +79,7 @@ public static bool TryGetEnumFieldName(this ITypeSymbol symbol, object value, [N continue; } - if (fieldValue == value) + if (fieldValue.Equals(value)) { fieldName = fieldSymbol.Name; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 5b7badbb1..491008c08 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -290,7 +290,9 @@ public partial class MyControl : Control [DataRow("int", "int", "42")] [DataRow("int?", "int?", "0")] [DataRow("int?", "int?", "42")] - [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility.Collapsed")] + [DataRow("Visibility", "Visibility", "Visibility.Collapsed")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum", "(global::MyApp.MyEnum)5")] + [DataRow("global::MyApp.MyEnum", "global::MyApp.MyEnum", "(global::MyApp.MyEnum)(-5)")] public async Task SimpleProperty_WithExplicitValue_NotDefault( string dependencyPropertyType, string propertyType, @@ -318,6 +320,8 @@ public partial class MyControl : Control set => SetValue(NameProperty, value); } } + + public enum MyEnum { A } """; string @fixed = $$""" @@ -334,6 +338,69 @@ public partial class MyControl : Control [GeneratedDependencyProperty(DefaultValue = {{defaultValueExpression}})] public partial {{propertyType}} {|CS9248:Name|} { get; set; } } + + public enum MyEnum { A } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + TestState = { AdditionalReferences = + { + MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), + MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) + }} + }; + + await test.RunAsync(); + } + + [TestMethod] + public async Task SimpleProperty_WithExplicitValue_NotDefault_AddsNamespace() + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(Windows.UI.Xaml.Automation.AnnotationType), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata(Windows.UI.Xaml.Automation.AnnotationType.TrackChanges)); + + public Windows.UI.Xaml.Automation.AnnotationType [|Name|] + { + get => (Windows.UI.Xaml.Automation.AnnotationType)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Automation; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty(DefaultValue = AnnotationType.TrackChanges)] + public partial Windows.UI.Xaml.Automation.AnnotationType {|CS9248:Name|} { get; set; } + } """; CSharpCodeFixTest test = new(LanguageVersion.Preview) From b65e89975bfcb9b1a061a040121a5d66d97ca9cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 21 Dec 2024 19:02:02 +0100 Subject: [PATCH 087/126] Add .targets to .NET 9 folders too --- .../CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index 9f3c18d45..b199aff64 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -36,8 +36,10 @@ + + From 7524a0c2c0bda0d4ce9fd18dbe700100fbbb6560 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 16:49:14 +0100 Subject: [PATCH 088/126] Simplify code fixer tests --- ...harpCodeFixerTest{TAnalyzer,TCodeFixer}.cs | 11 ++++ ...ndencyPropertyOnManualPropertyCodeFixer.cs | 56 ++----------------- 2 files changed, 16 insertions(+), 51 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs index 6bbc2a8f0..16ba5a5d5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs @@ -2,12 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.WinUI; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Testing; +using Windows.Foundation; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; @@ -32,6 +36,13 @@ internal sealed class CSharpCodeFixTest : CSharpCodeFixTe public CSharpCodeFixTest(LanguageVersion languageVersion) { this.languageVersion = languageVersion; + + ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Point).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); + TestState.AnalyzerConfigFiles.Add(("/.editorconfig", "[*]\nend_of_line = lf")); } /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 491008c08..2384e3267 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -3,14 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using CommunityToolkit.WinUI; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Windows.Foundation; -using Windows.UI.ViewManagement; -using Windows.UI.Xaml; using CSharpCodeFixTest = CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers.CSharpCodeFixTest< CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyAnalyzer, CommunityToolkit.GeneratedDependencyProperty.UseGeneratedDependencyPropertyOnManualPropertyCodeFixer>; @@ -110,15 +104,7 @@ public class MyClass { } CSharpCodeFixTest test = new(LanguageVersion.Preview) { TestCode = original, - FixedCode = @fixed, - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, - TestState = { AdditionalReferences = - { - MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) - }} + FixedCode = @fixed }; await test.RunAsync(); @@ -178,15 +164,7 @@ public enum MyEnum { A, B, C } CSharpCodeFixTest test = new(LanguageVersion.Preview) { TestCode = original, - FixedCode = @fixed, - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, - TestState = { AdditionalReferences = - { - MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) - }} + FixedCode = @fixed }; await test.RunAsync(); @@ -270,15 +248,7 @@ public partial class MyControl : Control CSharpCodeFixTest test = new(LanguageVersion.Preview) { TestCode = original, - FixedCode = @fixed, - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, - TestState = { AdditionalReferences = - { - MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) - }} + FixedCode = @fixed }; await test.RunAsync(); @@ -345,15 +315,7 @@ public enum MyEnum { A } CSharpCodeFixTest test = new(LanguageVersion.Preview) { TestCode = original, - FixedCode = @fixed, - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, - TestState = { AdditionalReferences = - { - MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) - }} + FixedCode = @fixed }; await test.RunAsync(); @@ -406,15 +368,7 @@ public partial class MyControl : Control CSharpCodeFixTest test = new(LanguageVersion.Preview) { TestCode = original, - FixedCode = @fixed, - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, - TestState = { AdditionalReferences = - { - MetadataReference.CreateFromFile(typeof(Point).Assembly.Location), - MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location), - MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location) - }} + FixedCode = @fixed }; await test.RunAsync(); From 45029c6f3fe845d162375c4da95f6602b80863ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 17:42:45 +0100 Subject: [PATCH 089/126] Undo temporary hacks and workarounds --- .github/workflows/build.yml | 95 +++++++++++++++++-- Directory.Build.targets | 1 + .../AppServices.SourceGenerators.Tests.csproj | 4 + ...oolkit.AppServices.SourceGenerators.csproj | 5 + .../src/CommunityToolkit.AppServices.csproj | 6 +- ...ependencyInjection.SourceGenerators.csproj | 30 ++++++ ...lkit.Extensions.DependencyInjection.csproj | 5 + 7 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0a0661ca..0897a88f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,8 +60,8 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2, 3] # Temporary until we can get Uno/Wasm working - multitarget: ['uwp', 'wasdk'] # Temporary until we can get Uno/Wasm working + winui: [2, 3] + multitarget: ['uwp', 'wasdk', 'wasm', 'wpf', 'linuxgtk', 'macos', 'ios', 'android'] exclude: # WinUI 2 not supported on wasdk - winui: 2 @@ -134,7 +134,42 @@ jobs: # Generate full solution with all projects (sample gallery heads, components, tests) - name: Generate solution with ${{ matrix.multitarget }} gallery, components and tests - run: powershell -version 5.1 -command "./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -Release -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + working-directory: ./ + run: powershell -version 5.1 -command "./tooling/GenerateAllSolution.ps1 -MultiTargets ${{ matrix.multitarget }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} -WinUIMajorVersion ${{ matrix.winui }}" -ErrorAction Stop + + # Build solution + - name: MSBuild (With diagnostics) + if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} + run: > + msbuild.exe /restore /nowarn:MSB4011 + /p:Configuration=Release + /m + ${{ env.ENABLE_DIAGNOSTICS == 'true' && '/bl' || '' }} + /v:${{ env.MSBUILD_VERBOSITY }} + CommunityToolkit.AllComponents.sln + + - name: MSBuild + if: ${{ env.ENABLE_DIAGNOSTICS == 'false' }} + run: msbuild.exe CommunityToolkit.AllComponents.sln /restore /nowarn:MSB4011 -p:Configuration=Release + + # Run tests + - name: Setup VSTest Path + uses: darenm/setup-vstest@3a16d909a1f3bbc65b52f8270d475d905e7d3e44 + + - name: Install Testspace Module + uses: testspace-com/setup-testspace@v1 + with: + domain: ${{ github.repository_owner }} + + - name: Run component tests against ${{ matrix.multitarget }} + if: ${{ matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk' }} + id: test-platform + run: vstest.console.exe ./tooling/**/CommunityToolkit.Tests.${{ matrix.multitarget }}.build.appxrecipe /Framework:FrameworkUap10 /logger:"trx;LogFileName=${{ matrix.multitarget }}.trx" /Blame + + - name: Create test reports + run: | + testspace '[${{ matrix.multitarget }}]./TestResults/*.trx' + if: ${{ (matrix.multitarget == 'uwp' || matrix.multitarget == 'wasdk') && (steps.test-generator.conclusion == 'success' || steps.test-platform.conclusion == 'success') }} - name: Artifact - Diagnostic Logs uses: actions/upload-artifact@v4 @@ -178,7 +213,7 @@ jobs: strategy: fail-fast: false # prevent one matrix pipeline from being cancelled if one fails, we want them all to run to completion. matrix: - winui: [2, 3] # Temporary until we can get Uno/Wasm working + winui: [2, 3] env: VERSION_PROPERTY: ${{ github.ref == 'refs/heads/main' && format('build.{0}', github.run_number) || format('pull-{0}.{1}', github.event.number, github.run_number) }} @@ -234,7 +269,7 @@ jobs: # Build and pack component nupkg - name: Build and pack component packages - run: ./tooling/Build-Toolkit-Components.ps1 -Components DependencyPropertyGenerator -MultiTargets uwp,wasdk -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release + run: ./tooling/Build-Toolkit-Components.ps1 -MultiTargets all -ExcludeMultiTargets ${{ env.EXCLUDED_MULTITARGETS }} -WinUIMajorVersion ${{ matrix.winui }} -DateForVersion ${{ env.VERSION_DATE }} ${{ env.VERSION_PROPERTY != '' && format('-PreviewVersion "{0}"', env.VERSION_PROPERTY) || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-EnableBinlogs' || '' }} ${{ env.ENABLE_DIAGNOSTICS == 'true' && '-Verbose' || '' }} -BinlogOutput ./ -NupkgOutput ./ -Release - name: Validate package names if: ${{ env.VERSION_PROPERTY != '' }} @@ -271,4 +306,52 @@ jobs: if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} with: name: build-logs-winui${{ matrix.winui }} - path: ./*.*log \ No newline at end of file + path: ./*.*log + + wasm-linux: + runs-on: ubuntu-latest + env: + HEADS_DIRECTORY: tooling/ProjectHeads + + steps: + - name: Install .NET SDK v${{ env.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: .NET Info (if diagnostics) + if: ${{ env.ENABLE_DIAGNOSTICS == 'true' }} + run: dotnet --info + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: recursive + + # Restore Tools from Manifest list in the Repository + - name: Restore dotnet tools + run: dotnet tool restore + + - name: Generate solution + shell: pwsh + working-directory: ./ + run: ./tooling/GenerateAllSolution.ps1${{ env.ENABLE_DIAGNOSTICS == 'true' && ' -UseDiagnostics' || '' }} + + - name: Install ninja for WASM native dependencies + run: sudo apt-get install ninja-build + + # Issue with Comment Links currently, see: https://github.com/mrlacey/CommentLinks/issues/38 + # See launch.json configuration file for analogous command we're emulating here to build LINK: ../../.vscode/launch.json:CommunityToolkit.App.Wasm.csproj + - name: dotnet build + working-directory: ./${{ env.HEADS_DIRECTORY }}/AllComponents/Wasm/ + run: dotnet build /r /bl /p:UnoSourceGeneratorUseGenerationHost=true /p:UnoSourceGeneratorUseGenerationController=false + + # TODO: Do we want to run tests here? Can we do that on linux easily? + + - name: Artifact - Diagnostic Logs + uses: actions/upload-artifact@v4 + if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }} + with: + name: linux-logs + path: ./**/*.*log \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets index a2a397dfa..77e29d5d4 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -26,6 +26,7 @@ + \ No newline at end of file diff --git a/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj b/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj index 3fceba82d..46b9111b5 100644 --- a/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj +++ b/components/AppServices/AppServices.SourceGenerators.Tests/AppServices.SourceGenerators.Tests.csproj @@ -16,4 +16,8 @@ + + + + diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj index 78f3c3ccc..64d80c88a 100644 --- a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/CommunityToolkit.AppServices.SourceGenerators.csproj @@ -22,4 +22,9 @@ + + + + + diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index c7db44f7b..cf4e183b4 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -7,7 +7,6 @@ CommunityToolkit.AppServices $(PackageIdPrefix).$(ToolkitComponentName) false - false @@ -56,4 +55,9 @@ + + + + + diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj new file mode 100644 index 000000000..e80e6cf87 --- /dev/null +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.csproj @@ -0,0 +1,30 @@ + + + netstandard2.0 + false + true + + + $(NoWarn);CS8500 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj index 4aa44f5af..34c3bb7e4 100644 --- a/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj +++ b/components/Extensions.DependencyInjection/src/CommunityToolkit.Extensions.DependencyInjection.csproj @@ -30,4 +30,9 @@ + + + + + From f8886736eb238f1d142cda37653c72ccf1f23837 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 17:45:17 +0100 Subject: [PATCH 090/126] Fix tooling submodule pointer --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 4d331c6b2..b121eb57c 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 4d331c6b24e258afcd42d28ebf63bea781744541 +Subproject commit b121eb57cc0fdca03206a9e1a08960d7e3cd824c From 079da46fa395a6c1483293f4ff9c6753e9d35adc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 17:48:37 +0100 Subject: [PATCH 091/126] Only build for UWP and WindowsAppSDK --- components/DependencyPropertyGenerator/src/MultiTarget.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/src/MultiTarget.props b/components/DependencyPropertyGenerator/src/MultiTarget.props index b11c19426..6a4600344 100644 --- a/components/DependencyPropertyGenerator/src/MultiTarget.props +++ b/components/DependencyPropertyGenerator/src/MultiTarget.props @@ -4,6 +4,6 @@ MultiTarget is a custom property that indicates which target a project is designed to be built for / run on. Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages. --> - uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + uwp;wasdk; \ No newline at end of file From cce95d374cf39918a54ee2fa550ff439b0d13555 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 19:32:39 +0100 Subject: [PATCH 092/126] Fix TFMs for UWP projects --- .../AppServices/src/CommunityToolkit.AppServices.csproj | 4 ++++ .../CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 3 +++ 2 files changed, 7 insertions(+) diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index cf4e183b4..322750f44 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -6,7 +6,11 @@ true CommunityToolkit.AppServices $(PackageIdPrefix).$(ToolkitComponentName) + false false + + + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index b199aff64..ffe37c06a 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -9,6 +9,9 @@ CommunityToolkit.WinUI.DependencyPropertyGeneratorRns false false + + + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; From a4ced343df8b67613377989bfb5514df6604e0ba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 19:33:09 +0100 Subject: [PATCH 093/126] Simplify packed .targets files --- ...munityToolkit.WinUI.DependencyPropertyGenerator.csproj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index ffe37c06a..344bbdf47 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -38,12 +38,8 @@ - - - - - - + + From 65fdb63f95235b24e9e69ef2c4badaee8804ef04 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 20:25:23 +0100 Subject: [PATCH 094/126] Disable 'UseUwpTools' property --- ...ommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index 344bbdf47..f86fb87a2 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -10,6 +10,12 @@ false false + + false + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; From 5a89531e90e83607aab463595789e5f1d8b1f43d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Dec 2024 22:38:20 +0100 Subject: [PATCH 095/126] Fix filenames for packaged .targets files --- ...unityToolkit.WinUI.DependencyPropertyGenerator.csproj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index f86fb87a2..a88fb0996 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -43,9 +43,12 @@ - - - + + + From ea7190925ea9e2206619d5445828b4902eda08c3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 02:32:35 +0100 Subject: [PATCH 096/126] Fix typos in .targets file --- ...mmunityToolkit.WinUI.DependencyPropertyGenerator.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index 933629788..ced7e4f8a 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -2,9 +2,9 @@ - true - true - false + true + true + false From a2f339c1bcd68c8f57acf7773ae7f7b23e936c31 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 02:37:35 +0100 Subject: [PATCH 097/126] Fix .targets again, add basic test --- .../CommunityToolkit.DependencyPropertyGenerator.Tests.csproj | 3 +++ ...CommunityToolkit.WinUI.DependencyPropertyGenerator.targets | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj index ea88d28b9..94514510f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/CommunityToolkit.DependencyPropertyGenerator.Tests.csproj @@ -40,4 +40,7 @@ Link="EmbeddedResources\GeneratedDependencyPropertyAttribute.cs" LogicalName="GeneratedDependencyPropertyAttribute.g.cs" /> + + + diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets index ced7e4f8a..612e2bd54 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.targets @@ -50,8 +50,8 @@ + DependsOnTargets="ResolveAssemblyReferences" + BeforeTargets="CoreCompile"> - uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; + + uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index a88fb0996..c400281b5 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -16,8 +16,11 @@ --> false - - uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; + + uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; diff --git a/components/Notifications/src/CommunityToolkit.Notifications.csproj b/components/Notifications/src/CommunityToolkit.Notifications.csproj index bc175d9ac..37b8fd8c0 100644 --- a/components/Notifications/src/CommunityToolkit.Notifications.csproj +++ b/components/Notifications/src/CommunityToolkit.Notifications.csproj @@ -16,8 +16,11 @@ false false - - uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; + + uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; From 9924f775ba7ce00c47963a7a8a03aadc814131fd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 13:28:25 +0100 Subject: [PATCH 100/126] Add more code fixer tests --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 2384e3267..5b2d20d22 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -373,4 +373,146 @@ public partial class MyControl : Control await test.RunAsync(); } + + [TestMethod] + public async Task MultipleProperties_HandlesSpacingCorrectly() + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public static readonly DependencyProperty Name2Property = DependencyProperty.Register( + name: "Name2", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? [|Name1|] + { + get => (string?)GetValue(Name1Property); + set => SetValue(Name1Property, value); + } + + public string? [|Name2|] + { + get => (string?)GetValue(Name2Property); + set => SetValue(Name2Property, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name1|} { get; set; } + + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name2|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } + + [TestMethod] + public async Task MultipleProperties_WithInterspersedMembers_HandlesSpacingCorrectly() + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public static readonly DependencyProperty Name2Property = DependencyProperty.Register( + name: "Name2", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// This is another member + public int Blah => 42; + + public string? [|Name1|] + { + get => (string?)GetValue(Name1Property); + set => SetValue(Name1Property, value); + } + + public string? [|Name2|] + { + get => (string?)GetValue(Name2Property); + set => SetValue(Name2Property, value); + } + } + """; + + // There is an extra leading blank line here for now, likely a 'SyntaxEditor' bug + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + + /// This is another member + public int Blah => 42; + + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name1|} { get; set; } + + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name2|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } } From 0c45212918a8d15fd349ebeae40c7f419643e90a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 14:21:47 +0100 Subject: [PATCH 101/126] Improve handling of known constants --- .../Models/TypedConstantInfo.cs | 160 +++++++++++++++--- 1 file changed, 138 insertions(+), 22 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index 18b781dd4..6e3104de9 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using CommunityToolkit.GeneratedDependencyProperty.Helpers; @@ -95,32 +98,145 @@ public override string ToString() public sealed record Of(T Value) : TypedConstantInfo where T : unmanaged, IEquatable { + /// + /// The cached map of constant fields for the type. + /// + private static readonly FrozenDictionary ConstantFields = GetConstantFields(); + /// public override string ToString() { - LiteralExpressionSyntax expressionSyntax = LiteralExpression(SyntaxKind.NumericLiteralExpression, Value switch + static ExpressionSyntax GetExpression(T value) + { + // Try to match named constants first + if (TryGetConstantExpression(value, out ExpressionSyntax? expression)) + { + return expression; + } + + // Special logic for doubles + if (value is double d) + { + // Handle 'double.NaN' explicitly, as 'ToString()' won't work on it at all + if (double.IsNaN(d)) + { + return MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + PredefinedType(Token(SyntaxKind.DoubleKeyword)), IdentifierName("NaN")); + } + + // Handle 0, to avoid matching against positive/negative zeros + if (d == 0) + { + return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal("0.0", 0.0)); + } + + string rawLiteral = d.ToString("R", CultureInfo.InvariantCulture); + + // For doubles, we need to manually format it and always add the trailing "D" suffix. + // This ensures that the correct type is produced if the expression was assigned to + // an object (eg. the literal was used in an attribute object parameter/property). + string literal = rawLiteral.Contains(".") ? rawLiteral : $"{rawLiteral}D"; + + return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(literal, d)); + } + + // Same special handling for floats as well + if (value is float f) + { + // Handle 'float.NaN' as above + if (float.IsNaN(f)) + { + return MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + PredefinedType(Token(SyntaxKind.FloatKeyword)), IdentifierName("NaN")); + } + + // Handle 0, same as above too + if (f == 0) + { + return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal("0.0F", 0.0f)); + } + + // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed + return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(f)); + } + + // Handle all other supported types as well + return LiteralExpression(SyntaxKind.NumericLiteralExpression, value switch + { + byte b => Literal(b), + char c => Literal(c), + int i => Literal(i), + long l => Literal(l), + sbyte sb => Literal(sb), + short sh => Literal(sh), + uint ui => Literal(ui), + ulong ul => Literal(ul), + ushort ush => Literal(ush), + _ => throw new ArgumentException("Invalid primitive type") + }); + } + + return GetExpression(Value).NormalizeWhitespace(eol: "\n").ToFullString(); + } + + /// + /// Tries to get a constant expression for a given value. + /// + /// The value to try to get an expression for. + /// The resulting expression, if successfully retrieved. + /// The expression for , if available. + /// Thrown if is not of a supported type. + private static bool TryGetConstantExpression(T value, [NotNullWhen(true)] out ExpressionSyntax? expression) + { + if (ConstantFields.TryGetValue(value, out string? name)) { - byte b => Literal(b), - char c => Literal(c), - - // For doubles, we need to manually format it and always add the trailing "D" suffix. - // This ensures that the correct type is produced if the expression was assigned to - // an object (eg. the literal was used in an attribute object parameter/property). - double d => Literal(d.ToString("R", CultureInfo.InvariantCulture) + "D", d), - - // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed - float f => Literal(f), - int i => Literal(i), - long l => Literal(l), - sbyte sb => Literal(sb), - short sh => Literal(sh), - uint ui => Literal(ui), - ulong ul => Literal(ul), - ushort ush => Literal(ush), - _ => throw new ArgumentException("Invalid primitive type") - }); - - return expressionSyntax.NormalizeWhitespace(eol: "\n").ToFullString(); + SyntaxKind syntaxKind = value switch + { + byte => SyntaxKind.ByteKeyword, + char => SyntaxKind.CharKeyword, + double => SyntaxKind.DoubleKeyword, + float => SyntaxKind.FloatKeyword, + int => SyntaxKind.IntKeyword, + long => SyntaxKind.LongKeyword, + sbyte => SyntaxKind.SByteKeyword, + short => SyntaxKind.ShortKeyword, + uint => SyntaxKind.UIntKeyword, + ulong => SyntaxKind.ULongKeyword, + ushort => SyntaxKind.UShortKeyword, + _ => throw new ArgumentException("Invalid primitive type") + }; + + expression = MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + PredefinedType(Token(syntaxKind)), IdentifierName(name)); + + return true; + } + + expression = null; + + return false; + } + + /// + /// Gets a mapping of all well known constant fields for the current type. + /// + /// The mapping of all well known constant fields for the current type. + private static FrozenDictionary GetConstantFields() + { + return typeof(T) + .GetFields() + .Where(static info => info.IsLiteral) + .Where(static info => info.FieldType == typeof(T)) + .Select(static info => (Value: (T)info.GetRawConstantValue(), info.Name)) + .Where(static info => !EqualityComparer.Default.Equals(info.Value, default)) + .ToFrozenDictionary( + keySelector: static info => info.Value, + elementSelector: static info => info.Name); + + } } } From 500ed3657a9ba703739ae461b2635283edeae49b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 15:17:41 +0100 Subject: [PATCH 102/126] Improve number formatting, add fixer tests --- .../Models/TypedConstantInfo.cs | 11 +- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 141 +++++++++++++++++- 2 files changed, 143 insertions(+), 9 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs index 6e3104de9..4ffb03a31 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.cs @@ -136,7 +136,7 @@ static ExpressionSyntax GetExpression(T value) // For doubles, we need to manually format it and always add the trailing "D" suffix. // This ensures that the correct type is produced if the expression was assigned to // an object (eg. the literal was used in an attribute object parameter/property). - string literal = rawLiteral.Contains(".") ? rawLiteral : $"{rawLiteral}D"; + string literal = rawLiteral.Contains(".") ? rawLiteral : $"{rawLiteral}.0"; return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(literal, d)); } @@ -158,8 +158,13 @@ static ExpressionSyntax GetExpression(T value) return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal("0.0F", 0.0f)); } - // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed - return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(f)); + string rawLiteral = f.ToString("R", CultureInfo.InvariantCulture); + + // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed. + // However, we still format it manually to ensure we consistently add ".0" as suffix. + string literal = rawLiteral.Contains(".") ? $"{rawLiteral}F" : $"{rawLiteral}.0F"; + + return LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(literal, f)); } // Handle all other supported types as well diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 5b2d20d22..10d540254 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -324,7 +324,7 @@ public enum MyEnum { A } [TestMethod] public async Task SimpleProperty_WithExplicitValue_NotDefault_AddsNamespace() { - string original = $$""" + const string original = """ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -348,7 +348,7 @@ public Windows.UI.Xaml.Automation.AnnotationType [|Name|] } """; - string @fixed = $$""" + const string @fixed = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Automation; @@ -377,7 +377,7 @@ public partial class MyControl : Control [TestMethod] public async Task MultipleProperties_HandlesSpacingCorrectly() { - string original = $$""" + const string original = """ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -413,7 +413,7 @@ public string? [|Name2|] } """; - string @fixed = $$""" + const string @fixed = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -444,7 +444,7 @@ public partial class MyControl : Control [TestMethod] public async Task MultipleProperties_WithInterspersedMembers_HandlesSpacingCorrectly() { - string original = $$""" + const string original = """ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -484,7 +484,7 @@ public string? [|Name2|] """; // There is an extra leading blank line here for now, likely a 'SyntaxEditor' bug - string @fixed = $$""" + const string @fixed = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -515,4 +515,133 @@ public partial class MyControl : Control await test.RunAsync(); } + + [TestMethod] + [DataRow("float", "0.0F", "1.0F", "0.123F")] + [DataRow("double", "0.0", "4.0", "0.123")] + public async Task MultipleProperties_HandlesWellKnownLiterals(string propertyType, string zeroExpression, string literalExpression, string decimalLiteralExpression) + { + string original = $$""" + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty P1Property = DependencyProperty.Register( + name: "P1", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{zeroExpression}})); + + public static readonly DependencyProperty P2Property = DependencyProperty.Register( + name: "P2", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{propertyType}}.MinValue)); + + public static readonly DependencyProperty P3Property = DependencyProperty.Register( + name: "P3", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{propertyType}}.NaN)); + + public static readonly DependencyProperty P4Property = DependencyProperty.Register( + name: "P4", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{propertyType}}.Pi)); + + public static readonly DependencyProperty P5Property = DependencyProperty.Register( + name: "P5", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{literalExpression}})); + + public static readonly DependencyProperty P6Property = DependencyProperty.Register( + name: "P6", + propertyType: typeof({{propertyType}}), + ownerType: typeof(MyControl), + typeMetadata: new PropertyMetadata({{decimalLiteralExpression}})); + + public {{propertyType}} [|P1|] + { + get => ({{propertyType}})GetValue(P1Property); + set => SetValue(P1Property, value); + } + + public {{propertyType}} [|P2|] + { + get => ({{propertyType}})GetValue(P2Property); + set => SetValue(P2Property, value); + } + + public {{propertyType}} [|P3|] + { + get => ({{propertyType}})GetValue(P3Property); + set => SetValue(P3Property, value); + } + + public {{propertyType}} [|P4|] + { + get => ({{propertyType}})GetValue(P4Property); + set => SetValue(P4Property, value); + } + + public {{propertyType}} [|P5|] + { + get => ({{propertyType}})GetValue(P5Property); + set => SetValue(P5Property, value); + } + + public {{propertyType}} [|P6|] + { + get => ({{propertyType}})GetValue(P6Property); + set => SetValue(P6Property, value); + } + } + """; + + string @fixed = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + public partial {{propertyType}} {|CS9248:P1|} { get; set; } + + [GeneratedDependencyProperty(DefaultValue = {{propertyType}}.MinValue)] + public partial {{propertyType}} {|CS9248:P2|} { get; set; } + + [GeneratedDependencyProperty(DefaultValue = {{propertyType}}.NaN)] + public partial {{propertyType}} {|CS9248:P3|} { get; set; } + + [GeneratedDependencyProperty(DefaultValue = {{propertyType}}.Pi)] + public partial {{propertyType}} {|CS9248:P4|} { get; set; } + + [GeneratedDependencyProperty(DefaultValue = {{literalExpression}})] + public partial {{propertyType}} {|CS9248:P5|} { get; set; } + + [GeneratedDependencyProperty(DefaultValue = {{decimalLiteralExpression}})] + public partial {{propertyType}} {|CS9248:P6|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } } From f4ec6f5a536d1d0dcdb5ab72020ef47878d69d17 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 15:35:35 +0100 Subject: [PATCH 103/126] Bump SDK of UWP on .NET 9 to 19041 --- .../AppServices/src/CommunityToolkit.AppServices.csproj | 6 +++--- ...ommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 6 +++--- .../Notifications/src/CommunityToolkit.Notifications.csproj | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index f7581f94a..18a3ef41f 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -10,10 +10,10 @@ false - uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; + uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index c400281b5..e31bcb860 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -17,10 +17,10 @@ false - uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; + uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; diff --git a/components/Notifications/src/CommunityToolkit.Notifications.csproj b/components/Notifications/src/CommunityToolkit.Notifications.csproj index 37b8fd8c0..249b85a32 100644 --- a/components/Notifications/src/CommunityToolkit.Notifications.csproj +++ b/components/Notifications/src/CommunityToolkit.Notifications.csproj @@ -17,10 +17,10 @@ false - uap10.0.17763;net8.0-windows10.0.18362.0;net9.0-windows10.0.18362.0; + uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; From 53f3195a71e1383f41bd86a1a289312716fe2dcd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 26 Dec 2024 16:40:16 +0100 Subject: [PATCH 104/126] Remove Uno workaround, fix TFMs --- .../AppServices/src/CommunityToolkit.AppServices.csproj | 7 ++++--- ...mmunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 7 ++++--- .../src/CommunityToolkit.Notifications.csproj | 7 ++++--- tooling | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index 18a3ef41f..f218ee405 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -8,12 +8,13 @@ $(PackageIdPrefix).$(ToolkitComponentName) false false + false - uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index e31bcb860..8b4d7ad61 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -15,12 +15,13 @@ not a UWP library per se. So to support the 17763 SDK, we can just disable the UWP build tools. --> false + false - uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/components/Notifications/src/CommunityToolkit.Notifications.csproj b/components/Notifications/src/CommunityToolkit.Notifications.csproj index 249b85a32..4a27c5737 100644 --- a/components/Notifications/src/CommunityToolkit.Notifications.csproj +++ b/components/Notifications/src/CommunityToolkit.Notifications.csproj @@ -15,12 +15,13 @@ disable false false + false - uap10.0.17763;net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0; + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/tooling b/tooling index 93931e0be..eb797b54f 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 93931e0beda3520fcc68e8bf9975a4ebb7067674 +Subproject commit eb797b54f753257c68d724621a47ee38155d603d From f3613a34857a48ad3e8a45b3b868a02736a9a051 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 26 Dec 2024 14:14:40 +0100 Subject: [PATCH 105/126] Trivia handling --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 33 +++++++++++++++++++ ...ndencyPropertyOnManualPropertyCodeFixer.cs | 2 -- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 9004cfa8d..f51bee401 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -298,6 +298,39 @@ private static void ConvertToPartialProperty( syntaxEditor.ReplaceNode(propertyDeclaration, updatedPropertyDeclaration); + // Special handling for the leading trivia of members following the field declaration we are about to remove. + // There is an edge case that can happen when a type declaration is as follows: + // + // class ContainingType + // { + // public static readonly DependencyProperty NameProperty = ...; + // + // public void SomeOtherMember() { } + // + // public string? Name { ... } + // } + // + // In this case, just removing the target field for the dependency property being rewritten (that is, 'NameProperty') + // will cause an extra blank line to be left after the edits, right above the member immediately following the field. + // To work around this, we look for such a member and check its trivia, and then manually remove a leading blank line. + if (fieldDeclaration.Parent is TypeDeclarationSyntax fieldParentTypeDeclaration) + { + int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration); + + // Check whether there is a member immediatley following the field + if (fieldDeclarationIndex >= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1) + { + MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1]; + SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia(); + + // Check whether this member has a first leading trivia that's just a blank line: we want to remove this one + if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) + { + syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0))); + } + } + } + // Also remove the field declaration (it'll be generated now) syntaxEditor.RemoveNode(fieldDeclaration); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 10d540254..9860dcbde 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -483,7 +483,6 @@ public string? [|Name2|] } """; - // There is an extra leading blank line here for now, likely a 'SyntaxEditor' bug const string @fixed = """ using CommunityToolkit.WinUI; using Windows.UI.Xaml; @@ -495,7 +494,6 @@ namespace MyApp; public partial class MyControl : Control { - /// This is another member public int Blah => 42; From c0318083c29223d7ea781c4584d4e55628a9d694 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 26 Dec 2024 20:13:22 +0100 Subject: [PATCH 106/126] Use the lambda overload --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 105 ++++++++++-------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index f51bee401..36e0074b0 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -241,62 +241,69 @@ private static void ConvertToPartialProperty( SyntaxEditor syntaxEditor, string? defaultValueExpression) { - // Update the attribute to insert with the default value, if present - generatedDependencyPropertyAttributeList = UpdateGeneratedDependencyPropertyAttributeList( - document, - semanticModel, - generatedDependencyPropertyAttributeList, - defaultValueExpression); + // Replace the property with the partial property using the attribute. Note that it's important to use the + // lambda 'ReplaceNode' overload here, rather than creating a modifier property declaration syntax node and + // replacing the original one. Doing that would cause the following 'ReplaceNode' call to adjust the leading + // trivia of trailing members after the fields being removed to not work incorrectly, and fail to be resolved. + syntaxEditor.ReplaceNode(propertyDeclaration, (node, _) => + { + PropertyDeclarationSyntax propertyDeclaration = (PropertyDeclarationSyntax)node; - // Start setting up the updated attribute lists - SyntaxList attributeLists = propertyDeclaration.AttributeLists; + // Update the attribute to insert with the default value, if present + generatedDependencyPropertyAttributeList = UpdateGeneratedDependencyPropertyAttributeList( + document, + semanticModel, + generatedDependencyPropertyAttributeList, + defaultValueExpression); - if (attributeLists is [AttributeListSyntax firstAttributeListSyntax, ..]) - { - // Remove the trivia from the original first attribute - attributeLists = attributeLists.Replace( - nodeInList: firstAttributeListSyntax, - newNode: firstAttributeListSyntax.WithoutTrivia()); + // Start setting up the updated attribute lists + SyntaxList attributeLists = propertyDeclaration.AttributeLists; - // If the property has at least an attribute list, move the trivia from it to the new attribute - generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(firstAttributeListSyntax); + if (attributeLists is [AttributeListSyntax firstAttributeListSyntax, ..]) + { + // Remove the trivia from the original first attribute + attributeLists = attributeLists.Replace( + nodeInList: firstAttributeListSyntax, + newNode: firstAttributeListSyntax.WithoutTrivia()); - // Insert the new attribute - attributeLists = attributeLists.Insert(0, generatedDependencyPropertyAttributeList); - } - else - { - // Otherwise (there are no attribute lists), transfer the trivia to the new (only) attribute list - generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(propertyDeclaration); + // If the property has at least an attribute list, move the trivia from it to the new attribute + generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(firstAttributeListSyntax); - // Save the new attribute list - attributeLists = attributeLists.Add(generatedDependencyPropertyAttributeList); - } + // Insert the new attribute + attributeLists = attributeLists.Insert(0, generatedDependencyPropertyAttributeList); + } + else + { + // Otherwise (there are no attribute lists), transfer the trivia to the new (only) attribute list + generatedDependencyPropertyAttributeList = generatedDependencyPropertyAttributeList.WithTriviaFrom(propertyDeclaration); - // Get a new property that is partial and with semicolon token accessors - PropertyDeclarationSyntax updatedPropertyDeclaration = - propertyDeclaration - .AddModifiers(Token(SyntaxKind.PartialKeyword)) - .WithoutLeadingTrivia() - .WithAttributeLists(attributeLists) - .WithAdditionalAnnotations(Formatter.Annotation) - .WithAccessorList(AccessorList(List( - [ - // Keep the accessors (so we can easily keep all trivia, modifiers, attributes, etc.) but make them semicolon only - propertyDeclaration.AccessorList!.Accessors[0] - .WithBody(null) - .WithExpressionBody(null) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .WithAdditionalAnnotations(Formatter.Annotation), - propertyDeclaration.AccessorList!.Accessors[1] - .WithBody(null) - .WithExpressionBody(null) - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) - .WithTrailingTrivia(propertyDeclaration.AccessorList.Accessors[1].GetTrailingTrivia()) - .WithAdditionalAnnotations(Formatter.Annotation) - ])).WithTrailingTrivia(propertyDeclaration.AccessorList.GetTrailingTrivia())); + // Save the new attribute list + attributeLists = attributeLists.Add(generatedDependencyPropertyAttributeList); + } - syntaxEditor.ReplaceNode(propertyDeclaration, updatedPropertyDeclaration); + // Get a new property that is partial and with semicolon token accessors + return + propertyDeclaration + .AddModifiers(Token(SyntaxKind.PartialKeyword)) + .WithoutLeadingTrivia() + .WithAttributeLists(attributeLists) + .WithAdditionalAnnotations(Formatter.Annotation) + .WithAccessorList(AccessorList(List( + [ + // Keep the accessors (so we can easily keep all trivia, modifiers, attributes, etc.) but make them semicolon only + propertyDeclaration.AccessorList!.Accessors[0] + .WithBody(null) + .WithExpressionBody(null) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .WithAdditionalAnnotations(Formatter.Annotation), + propertyDeclaration.AccessorList!.Accessors[1] + .WithBody(null) + .WithExpressionBody(null) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + .WithTrailingTrivia(propertyDeclaration.AccessorList.Accessors[1].GetTrailingTrivia()) + .WithAdditionalAnnotations(Formatter.Annotation) + ])).WithTrailingTrivia(propertyDeclaration.AccessorList.GetTrailingTrivia())); + }); // Special handling for the leading trivia of members following the field declaration we are about to remove. // There is an edge case that can happen when a type declaration is as follows: From 9ae5fba5abc728c9cc1461f0a2c217d221763b94 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 02:59:31 +0100 Subject: [PATCH 107/126] Tweak WinRT types matching logic --- .../Extensions/WinRTExtensions.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs index de7983612..738cfac37 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/WinRTExtensions.cs @@ -30,7 +30,13 @@ public static bool IsWellKnownWinRTProjectedValueType(this ITypeSymbol symbol, b // projected enum type or struct type (ie. some projected value type in general, except // for 'Nullable' values), then we can just use 'null' and bypass creating the property // metadata. The WinRT runtime will automatically instantiate a default value for us. - if (symbol.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) || + if (symbol.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml))) + { + return true; + } + + // Special case for projected numeric types + if (symbol.Name is "Matrix3x2" or "Matrix4x4" or "Plane" or "Quaternion" or "Vector2" or "Vector3" or "Vector4" && symbol.IsContainedInNamespace("System.Numerics")) { return true; @@ -51,17 +57,17 @@ public static bool IsWellKnownWinRTProjectedValueType(this ITypeSymbol symbol, b // Lastly, special case the well known primitive types if (symbol.SpecialType is - SpecialType.System_Int32 or - SpecialType.System_Byte or - SpecialType.System_SByte or - SpecialType.System_Int16 or - SpecialType.System_UInt16 or - SpecialType.System_UInt32 or - SpecialType.System_Int64 or - SpecialType.System_UInt64 or - SpecialType.System_Char or - SpecialType.System_Single or - SpecialType.System_Double) + SpecialType.System_Int32 or + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_Int16 or + SpecialType.System_UInt16 or + SpecialType.System_UInt32 or + SpecialType.System_Int64 or + SpecialType.System_UInt64 or + SpecialType.System_Char or + SpecialType.System_Single or + SpecialType.System_Double) { return true; } From 74919d5cf1b7d594132c8110abf906e840f705f4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 11:54:14 +0100 Subject: [PATCH 108/126] Fix AppServices build --- components/AppServices/src/CommunityToolkit.AppServices.csproj | 2 +- tooling | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index f218ee405..eb38fa4fd 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -37,7 +37,7 @@ - + Windows Desktop Extensions for the UWP diff --git a/tooling b/tooling index eb797b54f..3e178bc8f 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit eb797b54f753257c68d724621a47ee38155d603d +Subproject commit 3e178bc8f3d0eceb4ef9e955542ea5ce3892ca8a From 7d5c559169ac541196afb2123867d8191d4f74db Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 12:35:03 +0100 Subject: [PATCH 109/126] Add more test coverage for XML docs --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 9860dcbde..c57f393db 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -441,6 +441,89 @@ public partial class MyControl : Control await test.RunAsync(); } + [TestMethod] + public async Task MultipleProperties_WithXmlDocs_HandlesSpacingCorrectly() + { + const string original = """ + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public abstract partial class MyObject : DependencyObject + { + /// + /// Blah. + /// + public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register( + nameof(TargetObject), + typeof(TElement?), + typeof(MyObject), + null); + + /// + /// Blah. + /// + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + nameof(Value), + typeof(TValue?), + typeof(MyObject), + null); + + /// + /// Blah. + /// + public TValue? [|Value|] + { + get => (TValue?)GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + /// + /// Blah. + /// + public TElement? [|TargetObject|] + { + get => (TElement?)GetValue(TargetObjectProperty); + set => SetValue(TargetObjectProperty, value); + } + } + """; + + const string @fixed = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public abstract partial class MyObject : DependencyObject + { + /// + /// Blah. + /// + [GeneratedDependencyProperty] + public partial TValue? {|CS9248:Value|} { get; set; } + + /// + /// Blah. + /// + [GeneratedDependencyProperty] + public partial TElement? {|CS9248:TargetObject|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } + [TestMethod] public async Task MultipleProperties_WithInterspersedMembers_HandlesSpacingCorrectly() { From 17bc9972d65ac2b893e3a88096ab5a575614d234 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 12:54:21 +0100 Subject: [PATCH 110/126] Fix handling of EOLs in removed members --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 97 ++++++++++++------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 36e0074b0..450519add 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; @@ -217,6 +218,8 @@ private static async Task ConvertToPartialProperty( syntaxEditor, defaultValueExpression); + RemoveLeftoverLeadingEndOfLines([fieldDeclaration], syntaxEditor); + // Create the new document with the single change return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); } @@ -305,39 +308,6 @@ private static void ConvertToPartialProperty( ])).WithTrailingTrivia(propertyDeclaration.AccessorList.GetTrailingTrivia())); }); - // Special handling for the leading trivia of members following the field declaration we are about to remove. - // There is an edge case that can happen when a type declaration is as follows: - // - // class ContainingType - // { - // public static readonly DependencyProperty NameProperty = ...; - // - // public void SomeOtherMember() { } - // - // public string? Name { ... } - // } - // - // In this case, just removing the target field for the dependency property being rewritten (that is, 'NameProperty') - // will cause an extra blank line to be left after the edits, right above the member immediately following the field. - // To work around this, we look for such a member and check its trivia, and then manually remove a leading blank line. - if (fieldDeclaration.Parent is TypeDeclarationSyntax fieldParentTypeDeclaration) - { - int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration); - - // Check whether there is a member immediatley following the field - if (fieldDeclarationIndex >= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1) - { - MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1]; - SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia(); - - // Check whether this member has a first leading trivia that's just a blank line: we want to remove this one - if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) - { - syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0))); - } - } - } - // Also remove the field declaration (it'll be generated now) syntaxEditor.RemoveNode(fieldDeclaration); @@ -352,6 +322,58 @@ private static void ConvertToPartialProperty( } } + /// + /// Removes any leftover leading end of lines on remaining members following any removed fields. + /// + /// The collection of all fields that have been removed. + /// The instance to use. + private static void RemoveLeftoverLeadingEndOfLines(IReadOnlyCollection fieldDeclarations, SyntaxEditor syntaxEditor) + { + foreach (FieldDeclarationSyntax fieldDeclaration in fieldDeclarations) + { + // Special handling for the leading trivia of members following the field declaration we are about to remove. + // There is an edge case that can happen when a type declaration is as follows: + // + // class ContainingType + // { + // public static readonly DependencyProperty NameProperty = ...; + // + // public void SomeOtherMember() { } + // + // public string? Name { ... } + // } + // + // In this case, just removing the target field for the dependency property being rewritten (that is, 'NameProperty') + // will cause an extra blank line to be left after the edits, right above the member immediately following the field. + // To work around this, we look for such a member and check its trivia, and then manually remove a leading blank line. + if (fieldDeclaration.Parent is TypeDeclarationSyntax fieldParentTypeDeclaration) + { + int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration); + + // Check whether there is a member immediatley following the field + if (fieldDeclarationIndex >= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1) + { + MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1]; + + // It's especially important to skip members that have been rmeoved. This would otherwise fail when computing + // the final document. We only care about fixing trivia for members that will still be present after all edits. + if (fieldDeclarations.Contains(nextMember)) + { + continue; + } + + SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia(); + + // Check whether this member has a first leading trivia that's just a blank line: we want to remove this one + if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) + { + syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0))); + } + } + } + } + } + /// /// A custom with the logic from . /// @@ -381,6 +403,10 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider // Create an editor to perform all mutations (across all edits in the file) SyntaxEditor syntaxEditor = new(root, fixAllContext.Solution.Services); + // Create the set to track all fields being removed, to adjust whitespaces + HashSet fieldDeclarations = []; + + // Step 1: rewrite all properties and remove the fields foreach (Diagnostic diagnostic in diagnostics) { // Get the current property declaration for the diagnostic @@ -407,8 +433,13 @@ private sealed class FixAllProvider : DocumentBasedFixAllProvider generatedDependencyPropertyAttributeList, syntaxEditor, defaultValue); + + fieldDeclarations.Add(fieldDeclaration); } + // Step 2: remove any leftover leading end of lines on members following fields that have been removed + RemoveLeftoverLeadingEndOfLines(fieldDeclarations, syntaxEditor); + return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot()); } } From 1dadd4d3979b1828e3722f40c1129de92a1f69a5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 16:14:36 +0100 Subject: [PATCH 111/126] Fix EOL handling in more scenarios --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 68 +++++--- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 159 ++++++++++++++++++ 2 files changed, 205 insertions(+), 22 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 450519add..27e9356ae 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -346,31 +346,55 @@ private static void RemoveLeftoverLeadingEndOfLines(IReadOnlyCollection= 0 && fieldDeclarationIndex < fieldParentTypeDeclaration.Members.Count - 1) - { - MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1]; - - // It's especially important to skip members that have been rmeoved. This would otherwise fail when computing - // the final document. We only care about fixing trivia for members that will still be present after all edits. - if (fieldDeclarations.Contains(nextMember)) - { - continue; - } - - SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia(); - - // Check whether this member has a first leading trivia that's just a blank line: we want to remove this one - if (leadingTrivia.Count > 0 && leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) - { - syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0))); - } - } + int fieldDeclarationIndex = fieldParentTypeDeclaration.Members.IndexOf(fieldDeclaration); + + // Check whether there is a member immediatley following the field + if (fieldDeclarationIndex == -1 || fieldDeclarationIndex >= fieldParentTypeDeclaration.Members.Count - 1) + { + continue; } + + MemberDeclarationSyntax nextMember = fieldParentTypeDeclaration.Members[fieldDeclarationIndex + 1]; + + // It's especially important to skip members that have been rmeoved. This would otherwise fail when computing + // the final document. We only care about fixing trivia for members that will still be present after all edits. + if (fieldDeclarations.Contains(nextMember)) + { + continue; + } + + SyntaxTriviaList leadingTrivia = nextMember.GetLeadingTrivia(); + + // Check whether this member has a first leading trivia that's just a blank line: we want to remove this one + if (leadingTrivia.Count == 0 || !leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) + { + continue; + } + + bool hasAnyPersistentPrecedingMemberDeclarations = false; + + // Last check: we only want to actually remove the end of line if there are no other members before the current + // one, that have persistend in the containing type after all edits. If that is not the case, that is, if there + // are other members before the current one, we want to keep that end of line. Otherwise, we'd end up with the + // current member being incorrectly declared right after the previous one, without a separating blank line. + for (int i = 0; i < fieldDeclarationIndex + 1; i++) + { + hasAnyPersistentPrecedingMemberDeclarations |= !fieldDeclarations.Contains(fieldParentTypeDeclaration.Members[i]); + } + + // If there's any other persistent members, stop here + if (hasAnyPersistentPrecedingMemberDeclarations) + { + continue; + } + + // Finally, we can actually remove this end of line trivia, as we're sure it's not actually intended + syntaxEditor.ReplaceNode(nextMember, (nextMember, _) => nextMember.WithLeadingTrivia(leadingTrivia.RemoveAt(0))); } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index c57f393db..2e25ec9dc 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -597,6 +597,165 @@ public partial class MyControl : Control await test.RunAsync(); } + [TestMethod] + public async Task MultipleProperties_WithLeadingPersistentMembers_HandlesSpacingCorrectly() + { + const string original = """ + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public static readonly DependencyProperty Name2Property = DependencyProperty.Register( + name: "Name2", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name1 + { + get => (string?)GetValue(Name1Property) ?? string.Empty; + set => SetValue(Name1Property, value); + } + + public string? [|Name2|] + { + get => (string?)GetValue(Name2Property); + set => SetValue(Name2Property, value); + } + } + """; + + const string @fixed = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name1 + { + get => (string?)GetValue(Name1Property) ?? string.Empty; + set => SetValue(Name1Property, value); + } + + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name2|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } + + [TestMethod] + public async Task MultipleProperties_WithLeadingPersistentMembers_WithXmlDocs_HandlesSpacingCorrectly() + { + const string original = """ + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + /// Blah + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// Blah + public static readonly DependencyProperty Name2Property = DependencyProperty.Register( + name: "Name2", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// Blah + public string? Name1 + { + get => (string?)GetValue(Name1Property) ?? string.Empty; + set => SetValue(Name1Property, value); + } + + /// Blah + public string? [|Name2|] + { + get => (string?)GetValue(Name2Property); + set => SetValue(Name2Property, value); + } + } + """; + + const string @fixed = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + /// Blah + public static readonly DependencyProperty Name1Property = DependencyProperty.Register( + name: "Name1", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// Blah + public string? Name1 + { + get => (string?)GetValue(Name1Property) ?? string.Empty; + set => SetValue(Name1Property, value); + } + + /// Blah + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name2|} { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } + [TestMethod] [DataRow("float", "0.0F", "1.0F", "0.123F")] [DataRow("double", "0.0", "4.0", "0.123")] From f456963bd3e3eca77ac1152aed6d7758a33a2488 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 16:39:31 +0100 Subject: [PATCH 112/126] Respect accessibility of generated accessors --- .../DependencyPropertyGenerator.Execute.cs | 12 +- .../Test_DependencyPropertyGenerator.cs | 166 +++++++++++++++--- 2 files changed, 147 insertions(+), 31 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 99f7c4270..1c2208cf6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -554,8 +554,8 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) if (propertyInfo.IsLocalCachingEnabled) { writer.WriteLine($$""" - get => field; - set + {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get => field; + {{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set { On{{propertyInfo.PropertyName}}Set(ref value); @@ -602,7 +602,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) // would introduce a lot of overhead. If callers really do want to have a callback being invoked, they can implement // the one wired up to the property metadata directly. We can still invoke the ones only using the new value, though. writer.WriteLine($$""" - get + {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get { object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property); @@ -610,7 +610,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) return __boxedValue; } - set + {{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set { On{{propertyInfo.PropertyName}}Set(ref value); @@ -624,7 +624,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) { // Same as above but with the extra typed hook for both accessors writer.WriteLine($$""" - get + {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get { object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property); @@ -636,7 +636,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) return __unboxedValue; } - set + {{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set { On{{propertyInfo.PropertyName}}Set(ref value); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index a84b3f2d0..f531672f5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -15,8 +15,8 @@ public partial class Test_DependencyPropertyGenerator public void SingleProperty_Int32_WithLocalCache() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -141,8 +141,8 @@ public partial int Number public void SingleProperty_Int32_WithLocalCache_WithCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -321,8 +321,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_Int32_WithLocalCache_WithDefaultValue() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -447,8 +447,8 @@ public partial int Number public void SingleProperty_Int32_WithNoCaching() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -563,8 +563,8 @@ public partial int Number public void SingleProperty_Int32_WithNoCaching_UnsetValue() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -679,8 +679,8 @@ public partial int Number public void SingleProperty_Int32_WithNoCaching_WithCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -849,8 +849,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_Int32_WithNoCaching_WithDefaultValue() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -965,8 +965,8 @@ public partial int Number public void SingleProperty_Int32_WithNoCaching_WithDefaultValue_WithCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1135,8 +1135,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_Int32_WithNoCaching_WithSharedCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1305,8 +1305,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_Int32_WithNoCaching_WithBothCallbacks() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1484,8 +1484,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_String_WithLocalCache() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1610,8 +1610,8 @@ public partial string? Name public void SingleProperty_String_WithNoCaching() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1726,8 +1726,8 @@ public partial string? Name public void SingleProperty_String_WithNoCaching_Required() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1842,8 +1842,8 @@ public required partial string Name public void SingleProperty_String_WithNoCaching_New() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -1963,8 +1963,8 @@ partial class MyControl public void SingleProperty_String_WithNoCaching_Virtual() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -2081,8 +2081,8 @@ public virtual partial string Name public void SingleProperty_String_WithNoCaching_Override(string modifiers) { string source = $$""" - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -2198,12 +2198,128 @@ partial class MyControl CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); } + [TestMethod] + public void SingleProperty_String_WithNoCaching_CustomAccessibility() + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + internal partial string Name { protected get; private set; } + } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal partial string Name + { + protected get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string __unboxedValue = (string)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + private set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } + [TestMethod] public void MultipleProperties_WithNoCaching_CorrectSpacing() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -2399,8 +2515,8 @@ public partial string? LastName public void MultipleProperties_WithNoCaching_WithJustOnePropertyCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -2650,8 +2766,8 @@ file sealed class PropertyChangedUnsafeAccessors public void MultipleProperties_WithNoCaching_WithSharedPropertyCallback() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -2915,8 +3031,8 @@ file sealed class PropertyChangedUnsafeAccessors public void MultipleProperties_WithNoCaching_WithMixedPropertyCallbacks() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -3194,8 +3310,8 @@ file sealed class PropertyChangedUnsafeAccessors public void MultipleProperties_WithNoCaching_WithMixedPropertyCallbacks2() { const string source = """ - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -3485,8 +3601,8 @@ file sealed class PropertyChangedUnsafeAccessors public void SingleProperty_Int32_WithNoCaching_WithDefaultValueCallback(string returnType) { string source = $$""" - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -3608,8 +3724,8 @@ public partial int Number public void SingleProperty_NullableOfInt32_WithNoCaching_WithDefaultValueCallback(string returnType) { string source = $$""" - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; namespace MyNamespace; @@ -3731,8 +3847,8 @@ public partial int? Number public void SingleProperty_String_WithNoCaching_WithDefaultValueCallback(string returnType) { string source = $$""" - using Windows.UI.Xaml; using CommunityToolkit.WinUI; + using Windows.UI.Xaml; #nullable enable @@ -3912,10 +4028,10 @@ public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized( string source = $$""" using System; using System.Collections.Generic; + using CommunityToolkit.WinUI; using Windows.Foundation; using Windows.Foundation.Numerics; using Windows.UI.Xaml; - using CommunityToolkit.WinUI; #nullable enable From 70d5857ee7357abf7eda4fd2425b4fc21bf749ae Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 16:45:37 +0100 Subject: [PATCH 113/126] Add 'SyntaxTriviaExtensions', code tweaks --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 3 ++- .../Extensions/SyntaxTriviaExtensions.cs | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxTriviaExtensions.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 27e9356ae..c1c7eff80 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading.Tasks; using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; @@ -371,7 +372,7 @@ private static void RemoveLeftoverLeadingEndOfLines(IReadOnlyCollection +/// Extension methods for the type. +/// +internal static class SyntaxTriviaExtensions +{ + /// + /// Deconstructs a into its value. + /// + /// The input value. + /// The resulting value for . + public static void Deconstruct(this SyntaxTrivia syntaxTrivia, out SyntaxKind syntaxKind) + { + syntaxKind = syntaxTrivia.Kind(); + } +} From 072c750fa2dc179ffbcb550f7477c9f14b0991a5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 18:30:15 +0100 Subject: [PATCH 114/126] Add 'AttributeInfo' type and associated logic --- .../Extensions/SymbolInfoExtensions.cs | 48 ++++++++ .../Extensions/SyntaxTokenExtensions.cs | 24 ++++ .../Models/AttributeInfo.cs | 105 ++++++++++++++++++ .../Models/TypedConstantInfo.Factory.cs | 92 ++++++++++++++- 4 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SymbolInfoExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxTokenExtensions.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SymbolInfoExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SymbolInfoExtensions.cs new file mode 100644 index 000000000..fff56a90f --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SymbolInfoExtensions.cs @@ -0,0 +1,48 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class SymbolInfoExtensions +{ + /// + /// Tries to get the resolved attribute type symbol from a given value. + /// + /// The value to check. + /// The resulting attribute type symbol, if correctly resolved. + /// Whether is resolved to a symbol. + /// + /// This can be used to ensure users haven't eg. spelled names incorrectly or missed a using directive. Normally, code would just + /// not compile if that was the case, but that doesn't apply for attributes using invalid targets. In that case, Roslyn will ignore + /// any errors, meaning the generator has to validate the type symbols are correctly resolved on its own. + /// + public static bool TryGetAttributeTypeSymbol(this SymbolInfo symbolInfo, [NotNullWhen(true)] out INamedTypeSymbol? typeSymbol) + { + ISymbol? attributeSymbol = symbolInfo.Symbol; + + // If no symbol is selected and there is a single candidate symbol, use that + if (attributeSymbol is null && symbolInfo.CandidateSymbols is [ISymbol candidateSymbol]) + { + attributeSymbol = candidateSymbol; + } + + // Extract the symbol from either the current one or the containing type + if ((attributeSymbol as INamedTypeSymbol ?? attributeSymbol?.ContainingType) is not INamedTypeSymbol resultingSymbol) + { + typeSymbol = null; + + return false; + } + + typeSymbol = resultingSymbol; + + return true; + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxTokenExtensions.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxTokenExtensions.cs new file mode 100644 index 000000000..0e29a31db --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Extensions/SyntaxTokenExtensions.cs @@ -0,0 +1,24 @@ +// 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; +using Microsoft.CodeAnalysis.CSharp; + +namespace CommunityToolkit.GeneratedDependencyProperty.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class SyntaxTokenExtensions +{ + /// + /// Deconstructs a into its value. + /// + /// The input value. + /// The resulting value for . + public static void Deconstruct(this SyntaxToken syntaxToken, out SyntaxKind syntaxKind) + { + syntaxKind = syntaxToken.Kind(); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs new file mode 100644 index 000000000..8ac269e7c --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/AttributeInfo.cs @@ -0,0 +1,105 @@ +// 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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace CommunityToolkit.GeneratedDependencyProperty.Models; + +/// +/// A model representing an attribute declaration. +/// +/// The type name of the attribute. +/// The values for all constructor arguments for the attribute. +/// The values for all named arguments for the attribute. +internal sealed record AttributeInfo( + string TypeName, + EquatableArray ConstructorArgumentInfo, + EquatableArray<(string Name, TypedConstantInfo Value)> NamedArgumentInfo) +{ + /// + /// Creates a new instance from a given syntax node. + /// + /// The symbol for the attribute type. + /// The instance for the current run. + /// The sequence of instances to process. + /// The cancellation token for the current operation. + /// The resulting instance, if available + /// Whether a resulting instance could be created. + public static bool TryCreate( + INamedTypeSymbol typeSymbol, + SemanticModel semanticModel, + IEnumerable arguments, + CancellationToken token, + [NotNullWhen(true)] out AttributeInfo? info) + { + string typeName = typeSymbol.GetFullyQualifiedName(); + + using ImmutableArrayBuilder constructorArguments = new(); + using ImmutableArrayBuilder<(string, TypedConstantInfo)> namedArguments = new(); + + foreach (AttributeArgumentSyntax argument in arguments) + { + // The attribute expression has to have an available operation to extract information from + if (semanticModel.GetOperation(argument.Expression, token) is not IOperation operation) + { + continue; + } + + // Try to get the info for the current argument + if (!TypedConstantInfo.TryCreate(operation, semanticModel, argument.Expression, token, out TypedConstantInfo? argumentInfo)) + { + info = null; + + return false; + } + + // Try to get the identifier name if the current expression is a named argument expression. If it + // isn't, then the expression is a normal attribute constructor argument, so no extra work is needed. + if (argument.NameEquals is { Name.Identifier.ValueText: string argumentName }) + { + namedArguments.Add((argumentName, argumentInfo)); + } + else + { + constructorArguments.Add(argumentInfo); + } + } + + info = new AttributeInfo( + typeName, + constructorArguments.ToImmutable(), + namedArguments.ToImmutable()); + + return true; + } + + /// + public override string ToString() + { + // Gather the constructor arguments + IEnumerable arguments = + ConstructorArgumentInfo + .Select(static arg => AttributeArgument(ParseExpression(arg.ToString()))); + + // Gather the named arguments + IEnumerable namedArguments = + NamedArgumentInfo.Select(static arg => + AttributeArgument(ParseExpression(arg.Value.ToString())) + .WithNameEquals(NameEquals(IdentifierName(arg.Name)))); + + // Get the attribute to emit + AttributeSyntax attributeDeclaration = Attribute(IdentifierName(TypeName), AttributeArgumentList(SeparatedList(arguments.Concat(namedArguments)))); + + return attributeDeclaration.NormalizeWhitespace(eol: "\n").ToFullString(); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index 8c49ad307..5e37311c8 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -3,11 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Helpers; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; namespace CommunityToolkit.GeneratedDependencyProperty.Models; @@ -65,11 +70,12 @@ public static TypedConstantInfo Create(TypedConstant arg) } /// - /// Creates a new instance from a given value. + /// Creates a new instance from a given instance. /// - /// The input value. + /// The input instance. /// A instance representing . /// Thrown if the input argument is not valid. + /// This method only supports constant values. public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out TypedConstantInfo? result) { // Validate that we do have some constant value @@ -113,4 +119,86 @@ public static bool TryCreate(IOperation operation, [NotNullWhen(true)] out Typed return true; } + + /// + /// Creates a new instance from a given instance. + /// + /// The input instance. + /// The that was used to retrieve . + /// The that was retrieved from. + /// The cancellation token for the current operation. + /// The resulting instance, if available. + /// Whether a resulting instance could be created. + /// Thrown if the input argument is not valid. + public static bool TryCreate( + IOperation operation, + SemanticModel semanticModel, + ExpressionSyntax expression, + CancellationToken token, + [NotNullWhen(true)] out TypedConstantInfo? result) + { + if (TryCreate(operation, out result)) + { + return true; + } + + if (operation is ITypeOfOperation typeOfOperation) + { + result = new Type(typeOfOperation.TypeOperand.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + return true; + } + + if (operation is IArrayCreationOperation) + { + string? elementTypeName = ((IArrayTypeSymbol?)operation.Type)?.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + // If the element type is not available (since the attribute wasn't checked), just default to object + elementTypeName ??= "object"; + + // Handle all possible ways of initializing arrays in attributes + IEnumerable? arrayElementExpressions = expression switch + { + ImplicitArrayCreationExpressionSyntax { Initializer.Expressions: { } expressions } => expressions, + ArrayCreationExpressionSyntax { Initializer.Expressions: { } expressions } => expressions, + CollectionExpressionSyntax { Elements: { } elements } => elements.OfType().Select(static element => element.Expression), + _ => null + }; + + // No element expressions found, just return an empty array + if (arrayElementExpressions is null) + { + result = new Array(elementTypeName, ImmutableArray.Empty); + + return true; + } + + using ImmutableArrayBuilder items = new(); + + // Enumerate all array elements and extract serialized info for them + foreach (ExpressionSyntax elementExpressions in arrayElementExpressions) + { + if (semanticModel.GetOperation(elementExpressions, token) is not IOperation initializationOperation) + { + goto Failure; + } + + if (!TryCreate(initializationOperation, semanticModel, elementExpressions, token, out TypedConstantInfo? elementInfo)) + { + goto Failure; + } + + items.Add(elementInfo); + } + + result = new Array(elementTypeName, items.ToImmutable()); + + return true; + } + + Failure: + result = null; + + return false; + } } From 9af0344ea75dec821e65684c60ca139fdb5477d3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 18:30:39 +0100 Subject: [PATCH 115/126] Gather forwarded attributes for generated fields --- .../DependencyPropertyGenerator.Execute.cs | 75 +++++++++++++++++++ .../DependencyPropertyGenerator.cs | 12 ++- .../Models/DependencyPropertyInfo.cs | 4 +- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 1c2208cf6..e5184ec1d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; @@ -393,6 +394,73 @@ public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol pr return false; } + /// + /// Gathers all forwarded attributes for the generated property. + /// + ///The input node. + /// The instance for the current run. + /// The collection of forwarded attributes to add new ones to. + /// The current collection of gathered diagnostics. + /// The cancellation token for the current operation. + public static void GetForwardedAttributes( + PropertyDeclarationSyntax node, + SemanticModel semanticModel, + CancellationToken token, + out ImmutableArray staticFieldAttributes) + { + using ImmutableArrayBuilder builder = new(); + + // Gather explicit forwarded attributes info + foreach (AttributeListSyntax attributeList in node.AttributeLists) + { + // Only look for the 'static' attribute target, which can be used to target the generated 'DependencyProperty' static field. + // Roslyn will normally emit a 'CS0657' warning (invalid target), but that is automatically suppressed by a dedicated diagnostic + // suppressor that recognizes uses of this target specifically to support '[GeneratedDependencyProperty]'. We can't use 'field' + // as trigger, as that's used for the actual 'field' keyword, when local caching is enabled. + if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.StaticKeyword)) + { + continue; + } + + token.ThrowIfCancellationRequested(); + + foreach (AttributeSyntax attribute in attributeList.Attributes) + { + // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the 'AttributeData' as usual. + // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps: + // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not + // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node. + // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute. + // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type. + // - We then go over each attribute argument expression and get the operation for it. This will still be available even + // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all + // constant values to build the 'AttributeInfo' model. After all, attributes only support constant values, 'typeof(T)' + // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively. + // - From the syntax, we can also determine the identifier names for named attribute arguments, if any. + // + // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the + // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the + // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway. + if (!semanticModel.GetSymbolInfo(attribute, token).TryGetAttributeTypeSymbol(out INamedTypeSymbol? attributeTypeSymbol)) + { + continue; + } + + IEnumerable attributeArguments = attribute.ArgumentList?.Arguments ?? []; + + // Try to extract the forwarded attribute + if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, token, out AttributeInfo? attributeInfo)) + { + continue; + } + + builder.Add(attributeInfo); + } + } + + staticFieldAttributes = builder.ToImmutable(); + } + /// /// Writes all implementations of partial dependency property declarations. /// @@ -514,6 +582,13 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) /// """, isMultiline: true); writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false); + + // Write any forwarded attributes + foreach (AttributeInfo attributeInfo in propertyInfo.StaticFieldAttributes) + { + writer.WriteLine(attributeInfo.ToString()); + } + writer.Write($$""" public static readonly global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}} {{propertyInfo.PropertyName}}Property = global::{{WellKnownTypeNames.DependencyProperty(propertyInfo.UseWindowsUIXaml)}}.Register( name: "{{propertyInfo.PropertyName}}", diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index fb9edc8b3..4abd428e5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -121,6 +121,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); + // Get any forwarded attributes + Execute.GetForwardedAttributes( + (PropertyDeclarationSyntax)context.TargetNode, + context.SemanticModel, + token, + out ImmutableArray staticFieldAttributes); + + token.ThrowIfCancellationRequested(); + // Finally, get the hierarchy too HierarchyInfo hierarchyInfo = HierarchyInfo.From(propertySymbol.ContainingType); @@ -141,7 +150,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IsPropertyChangedCallbackImplemented: isPropertyChangedCallbackImplemented, IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented, IsNet8OrGreater: isNet8OrGreater, - UseWindowsUIXaml: useWindowsUIXaml); + UseWindowsUIXaml: useWindowsUIXaml, + StaticFieldAttributes: staticFieldAttributes); }) .WithTrackingName(WellKnownTrackingNames.Execute) .Where(static item => item is not null)!; diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs index 428d9bf3d..a53c35f6f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs @@ -25,6 +25,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// Indicates whether the WinRT-based shared property changed callback is implemented. /// Indicates whether the current target is .NET 8 or greater. /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. +/// The attributes to emit on the generated static field, if any. internal sealed record DependencyPropertyInfo( HierarchyInfo Hierarchy, string PropertyName, @@ -40,4 +41,5 @@ internal sealed record DependencyPropertyInfo( bool IsPropertyChangedCallbackImplemented, bool IsSharedPropertyChangedCallbackImplemented, bool IsNet8OrGreater, - bool UseWindowsUIXaml); + bool UseWindowsUIXaml, + EquatableArray StaticFieldAttributes); From e56f8aef031c4c7a8ff010b903051d498d1c3009 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 18:46:43 +0100 Subject: [PATCH 116/126] Fix attributes generation, add unit tests --- .../DependencyPropertyGenerator.Execute.cs | 2 +- .../Models/TypedConstantInfo.Factory.cs | 2 +- .../Test_DependencyPropertyGenerator.cs | 136 ++++++++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index e5184ec1d..c310fe84d 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -586,7 +586,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) // Write any forwarded attributes foreach (AttributeInfo attributeInfo in propertyInfo.StaticFieldAttributes) { - writer.WriteLine(attributeInfo.ToString()); + writer.WriteLine($"[{attributeInfo}]"); } writer.Write($$""" diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs index 5e37311c8..b3c51677b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/TypedConstantInfo.Factory.cs @@ -149,7 +149,7 @@ public static bool TryCreate( return true; } - if (operation is IArrayCreationOperation) + if (operation is (IArrayCreationOperation or ICollectionExpressionOperation) and { Type: null or IArrayTypeSymbol }) { string? elementTypeName = ((IArrayTypeSymbol?)operation.Type)?.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index f531672f5..88b0021fb 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -4145,4 +4145,140 @@ public partial {{propertyType}} Name CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.CSharp13); } + + [TestMethod] + [DataRow("A", "global::MyNamespace.AAttribute()")] + [DataRow("B(42, 10)", "global::MyNamespace.BAttribute(42, 10)")] + [DataRow("""C(10, X = "Test", Y = 42)""", """global::MyNamespace.CAttribute(10, X = "Test", Y = 42)""")] + [DataRow("D(Foo.B, typeof(string), new[] { 1, 2, 3 })", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] + [DataRow("D(Foo.B, typeof(string), new int[] { 1, 2, 3 })", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] + [DataRow("D(Foo.B, typeof(string), [1, 2, 3])", "global::MyNamespace.DAttribute(global::MyNamespace.Foo.B, typeof(string), new int[] { 1, 2, 3 })")] + public void SingleProperty_String_WithNoCaching_WithForwardedAttribute( + string attributeDefinition, + string attributeForwarding) + { + string source = $$""" + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + [static: {{attributeDefinition}}] + public partial string? Name { get; set; } + } + + public class AAttribute : Attribute; + public class BAttribute(int X, int Y) : Attribute; + public class CAttribute(int Z) : Attribute + { + public string X { get; set; } + public int Y { get; set; } + } + public class DAttribute(Foo X, Type Y, int[] Z) : Attribute; + public enum Foo { A, B } + """; + + string result = $$""" + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [{{attributeForwarding}}] + public static readonly global::Windows.UI.Xaml.DependencyProperty NameProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial string? Name + { + get + { + object? __boxedValue = GetValue(NameProperty); + + OnNameGet(ref __boxedValue); + + string? __unboxedValue = (string?)__boxedValue; + + OnNameGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNameSet(ref value); + + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue); + + OnNameChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameGet(ref string? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref object? propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameSet(ref string? propertyValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNameChanged(string? newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNamePropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } } From f75dcd5f6bd79aec6b3098e0e00f489bcd90148c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 18:55:45 +0100 Subject: [PATCH 117/126] Update analyzer for attributes, add unit tests --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 11 ++- .../Test_Analyzers.cs | 70 +++++++++++++++++++ .../Test_DependencyPropertyGenerator.cs | 1 + 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 10881e9f5..64d5a18fa 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -11,6 +11,7 @@ using CommunityToolkit.GeneratedDependencyProperty.Helpers; using CommunityToolkit.GeneratedDependencyProperty.Models; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -479,14 +480,20 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla } // Find the parent field for the operation (we're guaranteed to only fine one) - if (context.Operation.Syntax.FirstAncestor()?.GetLocation() is not Location fieldLocation) + if (context.Operation.Syntax.FirstAncestor() is not { } fieldDeclaration) + { + return; + } + + // Ensure that the field only has attributes we can forward, or not attributes at all + if (fieldDeclaration.AttributeLists.Any(static list => list.Target is { Identifier: not SyntaxToken(SyntaxKind.FieldKeyword) })) { return; } fieldFlags.PropertyName = propertyName; fieldFlags.PropertyType = propertyTypeSymbol; - fieldFlags.FieldLocation = fieldLocation; + fieldFlags.FieldLocation = fieldDeclaration.GetLocation(); }, OperationKind.FieldInitializer); // Finally, we can consume this information when we finish processing the symbol diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index b073ab716..3ec48596e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -1417,6 +1417,40 @@ public enum MyEnum { A, B, C } await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + [TestMethod] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_WithInvalidAttribute_DoesNotWarn() + { + const string source = $$""" + using System; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + [property: Test] + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? Name + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + + public class TestAttribute : Attribute; + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + [TestMethod] [DataRow("string", "string")] [DataRow("string", "string?")] @@ -1474,6 +1508,42 @@ public class MyClass { } await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + [TestMethod] + [DataRow("[Test]")] + [DataRow("[field: Test]")] + public async Task UseGeneratedDependencyPropertyOnManualPropertyAnalyzer_ValidProperty_WithAttributeOnField_Warns(string attributeDeclaration) + { + string source = $$""" + using System; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyControl : Control + { + {{attributeDeclaration}} + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? {|WCTDP0017:Name|} + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + + public class TestAttribute : Attribute; + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + [TestMethod] [DataRow("string", "string", "null")] [DataRow("string", "string", "default(string)")] diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 88b0021fb..054bb8966 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -4158,6 +4158,7 @@ public void SingleProperty_String_WithNoCaching_WithForwardedAttribute( string attributeForwarding) { string source = $$""" + using System; using CommunityToolkit.WinUI; using Windows.UI.Xaml; From e662f7635b71b1bbadc63f3417c7453b44da23fc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 19:07:02 +0100 Subject: [PATCH 118/126] Update code fixer, add unit tests --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 6 ++ ...ndencyPropertyOnManualPropertyCodeFixer.cs | 71 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index c1c7eff80..502f297e6 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -285,6 +285,12 @@ private static void ConvertToPartialProperty( attributeLists = attributeLists.Add(generatedDependencyPropertyAttributeList); } + // Append any attributes we want to forward (any attributes on the field, they've already been validated) + foreach (AttributeListSyntax fieldAttributeList in fieldDeclaration.AttributeLists) + { + attributeLists = attributeLists.Add(fieldAttributeList.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.StaticKeyword)))); + } + // Get a new property that is partial and with semicolon token accessors return propertyDeclaration diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 2e25ec9dc..b2a8860be 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -374,6 +374,77 @@ public partial class MyControl : Control await test.RunAsync(); } + [TestMethod] + [DataRow("[A]", "[static: A]")] + [DataRow("""[Test(42, "Hello")]""", """[static: Test(42, "Hello")]""")] + [DataRow("""[field: Test(42, "Hello")]""", """[static: Test(42, "Hello")]""")] + [DataRow("""[A, Test(42, "Hello")]""", """[static: A, Test(42, "Hello")]""")] + [DataRow(""" + [A] + [Test(42, "Hello")] + """, """ + [static: A] + [static: Test(42, "Hello")] + """)] + public async Task SimpleProperty_WithForwardedAttributes( + string attributeDefinition, + string attributeForwarding) + { + string original = $$""" + using System; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + {{attributeDefinition}} + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: nameof(Name), + propertyType: typeof(string), + ownerType: typeof(MyControl), + typeMetadata: null); + + public string? [|Name|] + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + + public class AAttribute : Attribute; + public class TestAttribute(int X, string Y) : Attribute; + """; + + string @fixed = $$""" + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public partial class MyControl : Control + { + [GeneratedDependencyProperty] + {{attributeForwarding}} + public partial string? {|CS9248:Name|} { get; set; } + } + + public class AAttribute : Attribute; + public class TestAttribute(int X, string Y) : Attribute; + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } + [TestMethod] public async Task MultipleProperties_HandlesSpacingCorrectly() { From e617cf0423cd5ed4d0e4f5adf054f1c4c8a46cbc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 19:17:16 +0100 Subject: [PATCH 119/126] Add 'StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor' --- .../Diagnostics/SuppressionDescriptors.cs | 21 ++++++ ...DependencyPropertyDeclarationSuppressor.cs | 64 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/SuppressionDescriptors.cs create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/SuppressionDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/SuppressionDescriptors.cs new file mode 100644 index 000000000..9122fc28e --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/SuppressionDescriptors.cs @@ -0,0 +1,21 @@ +// 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; + +namespace CommunityToolkit.GeneratedDependencyProperty.Diagnostics; + +/// +/// A container for all instances for suppressed diagnostics by analyzers in this project. +/// +internal static class SuppressionDescriptors +{ + /// + /// Gets a for a property using [GeneratedDependencyProperty] with an attribute list targeting the 'static' keyword. + /// + public static readonly SuppressionDescriptor StaticPropertyAttributeListForGeneratedDependencyPropertyDeclaration = new( + id: "WCTDPSPR0001", + suppressedDiagnosticId: "CS0658", + justification: "Properties using [GeneratedDependencyProperty] can use [static:] attribute lists to forward attributes to the generated dependency property fields."); +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs new file mode 100644 index 000000000..f85778f60 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs @@ -0,0 +1,64 @@ +// 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.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.SuppressionDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// +/// A diagnostic suppressor to suppress CS0658 warnings for properties with [GeneratedDependencyProperty] using a [static:] attribute list. +/// +/// +/// That is, this diagnostic suppressor will suppress the following diagnostic: +/// +/// public partial class MyControl : Control +/// { +/// [GeneratedDependencyProperty] +/// [static: JsonIgnore] +/// public partial string? Name { get; set; } +/// } +/// +/// +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor : DiagnosticSuppressor +{ + /// + public override ImmutableArray SupportedSuppressions { get; } = [StaticPropertyAttributeListForGeneratedDependencyPropertyDeclaration]; + + /// + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + foreach (Diagnostic diagnostic in context.ReportedDiagnostics) + { + SyntaxNode? syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan); + + // Check that the target is effectively [static:] over a property declaration, which is the only case we are interested in + if (syntaxNode is AttributeTargetSpecifierSyntax { Parent: PropertyDeclarationSyntax propertyDeclaration, Identifier: SyntaxToken(SyntaxKind.StaticKeyword) }) + { + SemanticModel semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree); + + // Get the property symbol from the property declaration + ISymbol? declaredSymbol = semanticModel.GetDeclaredSymbol(propertyDeclaration, context.CancellationToken); + + // Check if the property is using [GeneratedDependencyProperty], in which case we should suppress the warning + if (declaredSymbol is IPropertySymbol propertySymbol && propertySymbol.HasAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols)) + { + context.ReportSuppression(Suppression.Create(StaticPropertyAttributeListForGeneratedDependencyPropertyDeclaration, diagnostic)); + } + } + } + } +} From 2a5f0fe55010b468dd9f2cbfeca7446e1955c0b5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 21:35:30 +0100 Subject: [PATCH 120/126] Add analyzer for forwarded attributes, and tests --- .../AnalyzerReleases.Shipped.md | 2 + .../DependencyPropertyGenerator.Execute.cs | 2 +- ...tyForwardedAttributeDeclarationAnalyzer.cs | 106 ++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 26 +++ .../Test_Analyzers.cs | 192 ++++++++++++++++++ 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyForwardedAttributeDeclarationAnalyzer.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md index e547e0ee3..55f8ed3fc 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/AnalyzerReleases.Shipped.md @@ -24,3 +24,5 @@ WCTDP0014 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenera WCTDP0015 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | WCTDP0016 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info | WCTDP0017 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Info | +WCTDP0018 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | +WCTDP0019 | CommunityToolkit.GeneratedDependencyPropertyDependencyPropertyGenerator | Error | diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index c310fe84d..1773b993f 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -414,7 +414,7 @@ public static void GetForwardedAttributes( foreach (AttributeListSyntax attributeList in node.AttributeLists) { // Only look for the 'static' attribute target, which can be used to target the generated 'DependencyProperty' static field. - // Roslyn will normally emit a 'CS0657' warning (invalid target), but that is automatically suppressed by a dedicated diagnostic + // Roslyn will normally emit a 'CS0658' warning (invalid target), but that is automatically suppressed by a dedicated diagnostic // suppressor that recognizes uses of this target specifically to support '[GeneratedDependencyProperty]'. We can't use 'field' // as trigger, as that's used for the actual 'field' keyword, when local caching is enabled. if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.StaticKeyword)) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyForwardedAttributeDeclarationAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyForwardedAttributeDeclarationAnalyzer.cs new file mode 100644 index 000000000..0824c140e --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/InvalidPropertyForwardedAttributeDeclarationAnalyzer.cs @@ -0,0 +1,106 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using CommunityToolkit.GeneratedDependencyProperty.Constants; +using CommunityToolkit.GeneratedDependencyProperty.Extensions; +using CommunityToolkit.GeneratedDependencyProperty.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.GeneratedDependencyProperty; + +/// +/// A diagnostic analyzer that generates an error when a property with [GeneratedDependencyProperty] is using invalid forwarded attributes. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidPropertyForwardedAttributeDeclarationAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + InvalidDependencyPropertyTargetedAttributeType, + InvalidDependencyPropertyTargetedAttributeTypeArgumentExpression + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterCompilationStartAction(static context => + { + // Get the XAML mode to use + bool useWindowsUIXaml = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.GetMSBuildBooleanPropertyValue(WellKnownPropertyNames.DependencyPropertyGeneratorUseWindowsUIXaml); + + // Get the '[GeneratedDependencyProperty]' symbol (there might be multiples, due to embedded mode) + ImmutableArray generatedDependencyPropertyAttributeSymbols = context.Compilation.GetTypesByMetadataName(WellKnownTypeNames.GeneratedDependencyPropertyAttribute); + + // Get the 'DependencyObject' symbol + if (context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.DependencyObject(useWindowsUIXaml)) is not { } dependencyObjectSymbol) + { + return; + } + + context.RegisterSymbolStartAction(context => + { + // Ensure that we have some target property to analyze (also skip implementation parts) + if (context.Symbol is not IPropertySymbol { PartialDefinitionPart: null } propertySymbol) + { + return; + } + + // If the property is not using '[GeneratedDependencyProperty]', there's nothing to do + if (!propertySymbol.TryGetAttributeWithAnyType(generatedDependencyPropertyAttributeSymbols, out AttributeData? attributeData)) + { + return; + } + + context.RegisterSyntaxNodeAction(context => + { + foreach (AttributeListSyntax attributeList in ((PropertyDeclarationSyntax)context.Node).AttributeLists) + { + // Only target attributes that would be forwarded, ignore all others + if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.StaticKeyword)) + { + continue; + } + + foreach (AttributeSyntax attribute in attributeList.Attributes) + { + // Emit a diagnostic (and stop here for this attribute) if we can't resolve the symbol for the attribute to forward + if (!context.SemanticModel.GetSymbolInfo(attribute, context.CancellationToken).TryGetAttributeTypeSymbol(out INamedTypeSymbol? attributeTypeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidDependencyPropertyTargetedAttributeType, + attribute.GetLocation(), + propertySymbol, + attribute.Name.ToFullString())); + + continue; + } + + IEnumerable attributeArguments = attribute.ArgumentList?.Arguments ?? []; + + // Also emit a diagnostic if we fail to create the object model for the forwarded attribute + if (!AttributeInfo.TryCreate(attributeTypeSymbol, context.SemanticModel, attributeArguments, context.CancellationToken, out _)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidDependencyPropertyTargetedAttributeTypeArgumentExpression, + attribute.GetLocation(), + propertySymbol, + attributeTypeSymbol)); + } + } + } + }, SyntaxKind.PropertyDeclaration); + }, SymbolKind.Property); + }); + } +} diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 61138c936..d86c378e5 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -236,4 +236,30 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Manual properties should be converted to partial properties using [GeneratedDependencyProperty] when possible, which is recommended (doing so makes the code less verbose and results in more optimized code).", helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' annotated with [GeneratedDependencyProperty] is using attribute '{1}' which was not recognized as a valid type (are you missing a using directive?)". + /// + public static readonly DiagnosticDescriptor InvalidDependencyPropertyTargetedAttributeType = new( + id: "WCTDP0018", + title: "Invalid dependency property targeted attribute type", + messageFormat: "The property '{0}' annotated with [GeneratedDependencyProperty] is using attribute '{1}' which was not recognized as a valid type (are you missing a using directive?)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "All attributes targeting the generated dependency property for a property annotated with [GeneratedDependencyProperty] must correctly be resolved to valid types.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); + + /// + /// "The property '{0}' annotated with [GeneratedDependencyProperty] is using attribute '{1}' with an invalid expression (are you passing any incorrect parameters to the attribute constructor?)". + /// + public static readonly DiagnosticDescriptor InvalidDependencyPropertyTargetedAttributeTypeArgumentExpression = new( + id: "WCTDP0019", + title: "Invalid dependency property targeted attribute expression", + messageFormat: "The property '{0}' annotated with [GeneratedDependencyProperty] is using attribute '{1}' with an invalid expression (are you passing any incorrect parameters to the attribute constructor?)", + category: typeof(DependencyPropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "All attributes targeting the generated dependency property for a property annotated with [GeneratedDependencyProperty] must have arguments using supported expressions.", + helpLinkUri: "https://aka.ms/toolkit/labs/windows"); } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs index 3ec48596e..30b0a2fb2 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_Analyzers.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.GeneratedDependencyProperty.Tests; @@ -1625,4 +1626,195 @@ public class MyClass { } await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_NoDependencyPropertyAttribute_DoesNotWarn() + { + const string source = """ + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_NoForwardedAttribute_DoesNotWarn() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + public string? Name { get; set; } + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_ValidForwardedAttribute_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + [static: Test] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_TypoInAttributeName_NotTargetingStatic_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + [Testt] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13, + [ + // /0/Test0.cs(9,6): error CS0246: The type or namespace name 'Testt' could not be found (are you missing a using directive or an assembly reference?) + DiagnosticResult.CompilerError("CS0246").WithSpan(9, 6, 9, 11).WithArguments("Testt"), + + // /0/Test0.cs(9,6): error CS0246: The type or namespace name 'TesttAttribute' could not be found (are you missing a using directive or an assembly reference?) + DiagnosticResult.CompilerError("CS0246").WithSpan(9, 6, 9, 11).WithArguments("TesttAttribute") + ]); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_MissingUsingDirective_Warns() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + namespace MyApp + { + public class MyControl : Control + { + [GeneratedDependencyProperty] + [static: {|WCTDP0018:Test|}] + public string? Name { get; set; } + } + } + + namespace MyAttributes + { + public class TestAttribute : Attribute; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_TypoInAttributeName_Warns() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + [static: {|WCTDP0018:Testt|}] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + // See https://github.com/CommunityToolkit/dotnet/issues/683 + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_InvalidExpressionOnFieldAttribute_Warns() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + [static: {|WCTDP0019:Test(TestAttribute.M)|}] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute + { + public static string M => ""; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } + + [TestMethod] + public async Task InvalidPropertyForwardedAttributeDeclarationAnalyzer_InvalidExpressionOnFieldAttribute_WithExistingParameter_Warns() + { + const string source = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + public class MyControl : Control + { + [GeneratedDependencyProperty] + [static: {|WCTDP0019:Test(TestAttribute.M)|}] + public string? Name { get; set; } + } + + public class TestAttribute(string P) : Attribute + { + public static string M => ""; + } + """; + + await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, LanguageVersion.CSharp13); + } } From 81e2ad30761265ed1896cd121855974ae414be72 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 21:37:37 +0100 Subject: [PATCH 121/126] Fix outdated comments --- .../AppServices/src/CommunityToolkit.AppServices.csproj | 5 +---- ...CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj | 5 +---- .../Notifications/src/CommunityToolkit.Notifications.csproj | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index eb38fa4fd..ef7579bf9 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -10,10 +10,7 @@ false false - + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj index 8b4d7ad61..de4d74d91 100644 --- a/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj +++ b/components/DependencyPropertyGenerator/src/CommunityToolkit.WinUI.DependencyPropertyGenerator.csproj @@ -17,10 +17,7 @@ false false - + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; diff --git a/components/Notifications/src/CommunityToolkit.Notifications.csproj b/components/Notifications/src/CommunityToolkit.Notifications.csproj index 4a27c5737..6db92daf4 100644 --- a/components/Notifications/src/CommunityToolkit.Notifications.csproj +++ b/components/Notifications/src/CommunityToolkit.Notifications.csproj @@ -17,10 +17,7 @@ false false - + uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0; From 8a048a82b020580d8e40838a8486529e77d73820 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 23:24:54 +0100 Subject: [PATCH 122/126] Strip trivia from forwarded attributes --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 5 +- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 91 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 502f297e6..8907683a8 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -285,10 +285,11 @@ private static void ConvertToPartialProperty( attributeLists = attributeLists.Add(generatedDependencyPropertyAttributeList); } - // Append any attributes we want to forward (any attributes on the field, they've already been validated) + // Append any attributes we want to forward (any attributes on the field, they've already been validated). + // We also need to strip all trivia, to avoid accidentally carrying over XML docs from the field declaration. foreach (AttributeListSyntax fieldAttributeList in fieldDeclaration.AttributeLists) { - attributeLists = attributeLists.Add(fieldAttributeList.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.StaticKeyword)))); + attributeLists = attributeLists.Add(fieldAttributeList.WithTarget(AttributeTargetSpecifier(Token(SyntaxKind.StaticKeyword))).WithoutTrivia()); } // Get a new property that is partial and with semicolon token accessors diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index b2a8860be..0d2cf9c09 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -955,4 +955,95 @@ public partial class MyControl : Control await test.RunAsync(); } + + [TestMethod] + public async Task MultipleProperties_WithXmlDocs_WithForwardedAttributes_TrimsAttributTrivia() + { + const string original = """ + using System; + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ExpressionProperty = DependencyProperty.Register( + nameof(Expression), + typeof(string), + typeof(MyObject), + null); + + /// + /// Identifies the dependency property. + /// + [Test(42, "Test")] + public static readonly DependencyProperty InputProperty = DependencyProperty.Register( + nameof(Input), + typeof(object), + typeof(MyObject), + null); + + /// + /// Blah. + /// + public string? [|Expression|] + { + get => (string?)GetValue(ExpressionProperty); + set => SetValue(ExpressionProperty, value); + } + + /// + /// Blah. + /// + public object? [|Input|] + { + get => (object?)GetValue(InputProperty); + set => SetValue(InputProperty, value); + } + } + + public class TestAttribute(int X, string Y) : Attribute; + """; + + const string @fixed = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + /// + /// Blah. + /// + [GeneratedDependencyProperty] + public partial string? {|CS9248:Expression|} { get; set; } + + /// + /// Blah. + /// + [GeneratedDependencyProperty] + [static: Test(42, "Test")] + public partial object? {|CS9248:Input|} { get; set; } + } + + public class TestAttribute(int X, string Y) : Attribute; + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } } From c36129fac3039efb23b7c72c1f593ee5a79a59ce Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 23:41:28 +0100 Subject: [PATCH 123/126] Add 'CSharpSuppressorTest' --- .../CSharpSuppressorTest{TSuppressor}.cs | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs new file mode 100644 index 000000000..bae55d74b --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs @@ -0,0 +1,160 @@ +// 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.Linq; +using CommunityToolkit.WinUI; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.Foundation; +using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; + +/// +/// A custom for testing diagnostic suppressors. +/// +/// The type of the suppressor to test. +// Adapted from https://github.com/ImmediatePlatform/Immediate.Validations +public sealed class CSharpSuppressorTest : CSharpAnalyzerTest + where TSuppressor : DiagnosticSuppressor, new() +{ + /// + /// The list of analyzers to run on the input code. + /// + private readonly List _analyzers = []; + + /// + /// Whether to enable unsafe blocks. + /// + private readonly bool _allowUnsafeBlocks; + + /// + /// The C# language version to use to parse code. + /// + private readonly LanguageVersion _languageVersion; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The source code to analyze. + /// Whether to enable unsafe blocks. + /// The language version to use to run the test. + public CSharpSuppressorTest( + string source, + bool allowUnsafeBlocks = true, + LanguageVersion languageVersion = LanguageVersion.CSharp13) + { + _allowUnsafeBlocks = allowUnsafeBlocks; + _languageVersion = languageVersion; + + TestCode = source; + TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Point).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); + } + + /// + protected override IEnumerable GetDiagnosticAnalyzers() + { + return base.GetDiagnosticAnalyzers().Concat(_analyzers); + } + + /// + protected override CompilationOptions CreateCompilationOptions() + { + return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: _allowUnsafeBlocks); + } + + /// + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(_languageVersion, DocumentationMode.Diagnose); + } + + /// + /// Adds a new analyzer to the set of analyzers to run on the input code. + /// + /// The type of analyzer to activate. + /// The current test instance. + public CSharpSuppressorTest WithAnalyzer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] string assemblyQualifiedTypeName) + { + _analyzers.Add((DiagnosticAnalyzer)Activator.CreateInstance(Type.GetType(assemblyQualifiedTypeName)!)!); + + return this; + } + + /// + /// Specifies the diagnostics to enable. + /// + /// The set of diagnostics. + /// The current test instance. + public CSharpSuppressorTest WithSpecificDiagnostics(params DiagnosticResult[] diagnostics) + { + ImmutableDictionary diagnosticOptions = diagnostics.ToImmutableDictionary( + descriptor => descriptor.Id, + descriptor => descriptor.Severity.ToReportDiagnostic()); + + // Transform to enable the diagnostics + Solution EnableDiagnostics(Solution solution, ProjectId projectId) + { + CompilationOptions options = + solution.GetProject(projectId)?.CompilationOptions + ?? throw new InvalidOperationException("Compilation options missing."); + + return solution.WithProjectCompilationOptions( + projectId, + options.WithSpecificDiagnosticOptions(diagnosticOptions)); + } + + SolutionTransforms.Clear(); + SolutionTransforms.Add(EnableDiagnostics); + + return this; + } + + /// + /// Specifies the diagnostics that should be produced. + /// + /// The set of diagnostics. + /// The current test instance. + public CSharpSuppressorTest WithExpectedDiagnosticsResults(params DiagnosticResult[] diagnostics) + { + ExpectedDiagnostics.AddRange(diagnostics); + + return this; + } +} + +/// +/// Extensions for . +/// +file static class DiagnosticSeverityExtensions +{ + /// + /// Converts a value into a one. + /// + public static ReportDiagnostic ToReportDiagnostic(this DiagnosticSeverity severity) + { + return severity switch + { + DiagnosticSeverity.Hidden => ReportDiagnostic.Hidden, + DiagnosticSeverity.Info => ReportDiagnostic.Info, + DiagnosticSeverity.Warning => ReportDiagnostic.Warn, + DiagnosticSeverity.Error => ReportDiagnostic.Error, + _ => throw new InvalidEnumArgumentException(nameof(severity), (int)severity, typeof(DiagnosticSeverity)), + }; + } +} From c372d84423a2303798a96bf40e14be50acf376ab Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 27 Dec 2024 23:41:40 +0100 Subject: [PATCH 124/126] Fix diagnostic suppressor, add tests --- ...DependencyPropertyDeclarationSuppressor.cs | 2 +- .../Test_DiagnosticSuppressors.cs | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DiagnosticSuppressors.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs index f85778f60..ce3875685 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Suppressors/StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor.cs @@ -46,7 +46,7 @@ public override void ReportSuppressions(SuppressionAnalysisContext context) SyntaxNode? syntaxNode = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(diagnostic.Location.SourceSpan); // Check that the target is effectively [static:] over a property declaration, which is the only case we are interested in - if (syntaxNode is AttributeTargetSpecifierSyntax { Parent: PropertyDeclarationSyntax propertyDeclaration, Identifier: SyntaxToken(SyntaxKind.StaticKeyword) }) + if (syntaxNode is AttributeTargetSpecifierSyntax { Parent.Parent: PropertyDeclarationSyntax propertyDeclaration, Identifier: SyntaxToken(SyntaxKind.StaticKeyword) }) { SemanticModel semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DiagnosticSuppressors.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DiagnosticSuppressors.cs new file mode 100644 index 000000000..f52ab2a27 --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DiagnosticSuppressors.cs @@ -0,0 +1,84 @@ +// 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.GeneratedDependencyProperty.Tests.Helpers; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests; + +[TestClass] +public class Test_DiagnosticSuppressors +{ + private static readonly DiagnosticResult CS0658 = DiagnosticResult.CompilerWarning("CS0658"); + + [TestMethod] + [DataRow("get")] + [DataRow("with")] + [DataRow("readonly")] + [DataRow("propdp")] + public async Task StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor_OtherTarget_NotSuppressed(string target) + { + await new CSharpSuppressorTest( + $$""" + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + public class MyObject : DependencyObject + { + [GeneratedDependencyProperty] + [{{target}}: Test] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """) + .WithSpecificDiagnostics(CS0658) + .RunAsync(); + } + + [TestMethod] + public async Task StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor_NoTriggerAttribute_NotSuppressed() + { + await new CSharpSuppressorTest( + """ + using System; + using Windows.UI.Xaml; + + public class MyObject : DependencyObject + { + [static: Test] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """) + .WithSpecificDiagnostics(CS0658) + .RunAsync(); + } + + [TestMethod] + public async Task StaticAttributeListTargetOnGeneratedDependencyPropertyDeclarationSuppressor_ValidUse_Suppressed() + { + await new CSharpSuppressorTest( + """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + public class MyObject : DependencyObject + { + [GeneratedDependencyProperty] + [static: Test] + public string? Name { get; set; } + } + + public class TestAttribute : Attribute; + """) + .WithSpecificDiagnostics(CS0658) + .RunAsync(); + } +} From e37d7dcc111626006a0f5bb1c274d734b229cfb4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 28 Dec 2024 01:13:18 +0100 Subject: [PATCH 125/126] Add interleaved non-fixable properties, add tests --- ...endencyPropertyOnManualPropertyAnalyzer.cs | 2 +- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 208 ++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs index 64d5a18fa..ee855ca3b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Diagnostics/Analyzers/UseGeneratedDependencyPropertyOnManualPropertyAnalyzer.cs @@ -531,7 +531,7 @@ void HandleSetAccessor(IPropertySymbol propertySymbol, PropertyFlags propertyFla // We are intentionally not handling combinations of nullable value types here. if (!SymbolEqualityComparer.Default.Equals(fieldFlags.PropertyType, pair.Key.Type)) { - return; + continue; } // Finally, check whether the field was valid (if so, we will have a valid location) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 0d2cf9c09..5e4f56916 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -1046,4 +1046,212 @@ public class TestAttribute(int X, string Y) : Attribute; await test.RunAsync(); } + + // Using 'object' for dependency properties is sometimes needed to work around an 'IReference' issue in some binding scenarios + [TestMethod] + public async Task MultipleProperties_WithInterspersedNonFixableProprty_HandlesAllPossibleProperties() + { + const string original = """ + using System; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DisableAnimationProperty = DependencyProperty.Register( + nameof(DisableAnimation), + typeof(bool), + typeof(MyObject), + new PropertyMetadata(false)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register( + nameof(HorizontalOffset), + typeof(object), + typeof(MyObject), + null); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsHorizontalOffsetRelativeProperty = DependencyProperty.Register( + nameof(IsHorizontalOffsetRelative), + typeof(bool), + typeof(MyObject), + new PropertyMetadata(false)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IsVerticalOffsetRelativeProperty = DependencyProperty.Register( + nameof(IsVerticalOffsetRelative), + typeof(bool), + typeof(MyObject), + new PropertyMetadata(false)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetScrollViewerProperty = DependencyProperty.Register( + nameof(TargetScrollViewer), + typeof(ScrollViewer), + typeof(MyObject), + null); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register( + nameof(VerticalOffset), + typeof(object), + typeof(MyObject), + null); + + /// + /// Gets or sets a value indicating whether the animation is disabled. The default value is . + /// + public bool [|DisableAnimation|] + { + get => (bool)GetValue(DisableAnimationProperty); + set => SetValue(DisableAnimationProperty, value); + } + + /// + /// Gets or sets the distance should be scrolled horizontally. + /// + public double? HorizontalOffset + { + get => (double?)GetValue(HorizontalOffsetProperty); + set => SetValue(HorizontalOffsetProperty, value); + } + + /// + /// Gets or sets a value indicating whether the horizontal offset is relative to the current offset. The default value is . + /// + public bool [|IsHorizontalOffsetRelative|] + { + get => (bool)GetValue(IsHorizontalOffsetRelativeProperty); + set => SetValue(IsHorizontalOffsetRelativeProperty, value); + } + + /// + /// Gets or sets a value indicating whether the vertical offset is relative to the current offset. The default value is . + /// + public bool [|IsVerticalOffsetRelative|] + { + get => (bool)GetValue(IsVerticalOffsetRelativeProperty); + set => SetValue(IsVerticalOffsetRelativeProperty, value); + } + + /// + /// Gets or sets the target . + /// + public ScrollViewer? [|TargetScrollViewer|] + { + get => (ScrollViewer?)GetValue(TargetScrollViewerProperty); + set => SetValue(TargetScrollViewerProperty, value); + } + + /// + /// Gets or sets the distance should be scrolled vertically. + /// + public double? VerticalOffset + { + get => (double?)GetValue(VerticalOffsetProperty); + set => SetValue(VerticalOffsetProperty, value); + } + } + """; + + const string @fixed = """ + using System; + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + using Windows.UI.Xaml.Controls; + + #nullable enable + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register( + nameof(HorizontalOffset), + typeof(object), + typeof(MyObject), + null); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register( + nameof(VerticalOffset), + typeof(object), + typeof(MyObject), + null); + + /// + /// Gets or sets a value indicating whether the animation is disabled. The default value is . + /// + [GeneratedDependencyProperty] + public partial bool {|CS9248:DisableAnimation|} { get; set; } + + /// + /// Gets or sets the distance should be scrolled horizontally. + /// + public double? HorizontalOffset + { + get => (double?)GetValue(HorizontalOffsetProperty); + set => SetValue(HorizontalOffsetProperty, value); + } + + /// + /// Gets or sets a value indicating whether the horizontal offset is relative to the current offset. The default value is . + /// + [GeneratedDependencyProperty] + public partial bool {|CS9248:IsHorizontalOffsetRelative|} { get; set; } + + /// + /// Gets or sets a value indicating whether the vertical offset is relative to the current offset. The default value is . + /// + [GeneratedDependencyProperty] + public partial bool {|CS9248:IsVerticalOffsetRelative|} { get; set; } + + /// + /// Gets or sets the target . + /// + [GeneratedDependencyProperty] + public partial ScrollViewer? {|CS9248:TargetScrollViewer|} { get; set; } + + /// + /// Gets or sets the distance should be scrolled vertically. + /// + public double? VerticalOffset + { + get => (double?)GetValue(VerticalOffsetProperty); + set => SetValue(VerticalOffsetProperty, value); + } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } } From 21f36847e770e4d28664a08ea175b4f5c5dabbc7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 28 Dec 2024 01:23:31 +0100 Subject: [PATCH 126/126] Add 'partial' for nested types too, add tests --- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 17 +++--- ...ndencyPropertyOnManualPropertyCodeFixer.cs | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 8907683a8..5197300bb 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.CodeFixers/UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -319,14 +319,17 @@ private static void ConvertToPartialProperty( // Also remove the field declaration (it'll be generated now) syntaxEditor.RemoveNode(fieldDeclaration); - // Find the parent type for the property - TypeDeclarationSyntax typeDeclaration = propertyDeclaration.FirstAncestorOrSelf()!; - - // Make sure it's partial (we create the updated node in the function to preserve the updated property declaration). - // If we created it separately and replaced it, the whole tree would also be replaced, and we'd lose the new property. - if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + // Find the parent type for the property (we need to do this for all ancestor types, as the type might be bested) + for (TypeDeclarationSyntax? typeDeclaration = propertyDeclaration.FirstAncestor(); + typeDeclaration is not null; + typeDeclaration = typeDeclaration.FirstAncestor()) { - syntaxEditor.ReplaceNode(typeDeclaration, static (node, generator) => generator.WithModifiers(node, generator.GetModifiers(node).WithPartial(true))); + // Make sure it's partial (we create the updated node in the function to preserve the updated property declaration). + // If we created it separately and replaced it, the whole tree would also be replaced, and we'd lose the new property. + if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + syntaxEditor.ReplaceNode(typeDeclaration, static (node, generator) => generator.WithModifiers(node, generator.GetModifiers(node).WithPartial(true))); + } } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs index 5e4f56916..b18a8db7c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_UseGeneratedDependencyPropertyOnManualPropertyCodeFixer.cs @@ -1254,4 +1254,60 @@ public double? VerticalOffset await test.RunAsync(); } + + [TestMethod] + public async Task SimpleProperty_NestedType_AddsAllRequiredPartialModifiers() + { + const string original = """ + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public class MyObject : DependencyObject + { + public class MyNestedObject : DependencyObject + { + public static readonly DependencyProperty NameProperty = DependencyProperty.Register( + name: "Name", + propertyType: typeof(string), + ownerType: typeof(MyNestedObject), + typeMetadata: null); + + public string? [|Name|] + { + get => (string?)GetValue(NameProperty); + set => SetValue(NameProperty, value); + } + } + } + """; + + const string @fixed = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + #nullable enable + + namespace MyApp; + + public partial class MyObject : DependencyObject + { + public partial class MyNestedObject : DependencyObject + { + [GeneratedDependencyProperty] + public partial string? {|CS9248:Name|} { get; set; } + } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed + }; + + await test.RunAsync(); + } }