Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧪 [Experiment] DependencyPropertyGenerator #624

Open
wants to merge 199 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
199 commits
Select commit Hold shift + click to select a range
054506c
Generated DependencyPropertyGenerator component from template
Arlodotexe Dec 2, 2024
4ed6172
Port files from private repo
Sergio0694 Dec 2, 2024
794d0c3
Cleanup and merge ported csproj, rename files
Arlodotexe Dec 2, 2024
bd3f0c5
Fix default namespace and package name for DependencyPropertyGenerato…
Arlodotexe Dec 2, 2024
28b5c83
Disable globalusings in DependencyPropertyGenerator
Arlodotexe Dec 2, 2024
8e7694f
Move files, fix folder name
Arlodotexe Dec 3, 2024
54f0445
Bring back embedded resources
Sergio0694 Dec 3, 2024
9aec601
Fix folder name typo
Arlodotexe Dec 3, 2024
f472ab0
Fix project references and update embedded resource paths in Dependen…
Arlodotexe Dec 3, 2024
a559c58
Fix wrong paths in test project
Sergio0694 Dec 3, 2024
bf62d4d
Fix more wrong paths
Sergio0694 Dec 3, 2024
eb854c9
Fix some warnings in the test project
Sergio0694 Dec 3, 2024
df93079
Create .gitattributes
Sergio0694 Dec 3, 2024
03f041e
Fix newlines to LF
Sergio0694 Dec 3, 2024
d503756
Use 'WellKnownTypeNames' in all analyzers
Sergio0694 Dec 3, 2024
8846a70
Remove global usings
Sergio0694 Dec 3, 2024
51d44bd
Finish 'InvalidPropertyDefaultValueTypeAnalyzer'
Sergio0694 Dec 3, 2024
6b2d0cb
Add unit tests
Sergio0694 Dec 3, 2024
6c1ceb7
Consolidate GlobalUsings imports around tooling
Arlodotexe Dec 3, 2024
d9bec94
Fixed namespace errors when running under Uno
Arlodotexe Dec 3, 2024
291f7fa
Update tooling submodule
Arlodotexe Dec 3, 2024
9637c3a
Remove LangVersion setting from DependencyPropertyGenerator project
Arlodotexe Dec 3, 2024
9f0809d
Ran XAML styler
Arlodotexe Dec 3, 2024
8436bde
Remove empty samples
Arlodotexe Dec 3, 2024
1babcec
Remove empty component doc to fix CI error
Arlodotexe Dec 3, 2024
c2351ac
Remove unused test files from DependencyPropertyGenerator project
Arlodotexe Dec 3, 2024
471c161
Update .NET version to 9.0 in Dockerfile, devcontainer.json, build.ym…
Arlodotexe Dec 5, 2024
9657827
Fixed compilation conditionals
Arlodotexe Dec 5, 2024
10499ac
Temporarily limit WinUI and multitarget options in build matrix for U…
Arlodotexe Dec 5, 2024
da2b2c3
Temporarily disable wasm-linux check, only build DependencyPropertyGe…
Arlodotexe Dec 5, 2024
cf30a64
Adjust CI to only build DependencyPropertyGenerator
Arlodotexe Dec 5, 2024
ec018ef
Enable WinUI 3, fix GenerateSingleSampleHeads script invocation
Arlodotexe Dec 5, 2024
c937c4c
Switch generator to WinAppSDK for now
Sergio0694 Dec 5, 2024
9acc0ce
Temp: Refactor build workflow to use Build-Toolkit-Components script …
Arlodotexe Dec 5, 2024
05edd93
Fix condition for local caching in generator
Sergio0694 Dec 5, 2024
c7ab831
Define 'DependencyPropertyGeneratorUseWindowsUIXaml'
Sergio0694 Dec 5, 2024
aff5de9
Generalize .dll reference check
Sergio0694 Dec 5, 2024
9ee417e
Add 'ForAttributeWithMetadataNameAndOptions'
Sergio0694 Dec 5, 2024
061f74e
Add 'AnalyzerConfigOptionsExtensions'
Sergio0694 Dec 5, 2024
6af9be8
Enable XAML option for generators/analyzers
Sergio0694 Dec 5, 2024
c100c0e
Enable Windows.UI.Xaml for legacy UWP
Sergio0694 Dec 5, 2024
9439f37
Add 'SyntaxKind' extensions
Sergio0694 Dec 6, 2024
eb41e28
Add support for more modifiers, bug fixes
Sergio0694 Dec 6, 2024
1b40caf
Add unit tests for more modifiers
Sergio0694 Dec 6, 2024
e152803
Add diagnostic and test for pointer types
Sergio0694 Dec 6, 2024
95590f0
Add 'DefaultValueCallback' property
Sergio0694 Dec 6, 2024
22ab868
Add initial support for default value callbacks
Sergio0694 Dec 6, 2024
c053699
Fix value callbacks, add unit tests
Sergio0694 Dec 7, 2024
b0e178e
Add '[DisallowNull]' to 'DefaultValueCallback'
Sergio0694 Dec 7, 2024
b4bf0a3
Add 'InvalidPropertyDefaultValueCallbackTypeAnalyzer'
Sergio0694 Dec 7, 2024
ba00de9
Add unit tests for new analyzer
Sergio0694 Dec 7, 2024
8f971c9
Fix codegen for all default value callback cases
Sergio0694 Dec 7, 2024
aa12670
Add 'PropertyDeclarationWithPropertyNameSuffixAnalyzer'
Sergio0694 Dec 7, 2024
e988aab
Fix two nullability warnings
Sergio0694 Dec 7, 2024
4a42758
Improve formatting for property initialization
Sergio0694 Dec 7, 2024
d40bee5
Bug fixes to embedded mode
Sergio0694 Dec 9, 2024
b48e37f
Update components/DependencyPropertyGenerator/CommunityToolkit.Depend…
Arlodotexe Dec 9, 2024
a943c7b
Update components/DependencyPropertyGenerator/CommunityToolkit.Depend…
Arlodotexe Dec 9, 2024
42d3211
Fix typos
Sergio0694 Dec 9, 2024
e481f48
Add draft 'UseGeneratedDependencyPropertyOnManualPropertyAnalyzer'
Sergio0694 Dec 12, 2024
ef70aab
Pass the location of the target field
Sergio0694 Dec 12, 2024
08707e6
Add empty 'CodeFixers' project
Sergio0694 Dec 12, 2024
2389d33
Bump 'Microsoft.CodeAnalysis.CSharp' to latest
Sergio0694 Dec 12, 2024
0281e36
Pack code fixers into NuGet package
Sergio0694 Dec 12, 2024
a032d85
Add 'InternalsVisibleTo' for code fixers
Sergio0694 Dec 12, 2024
8524021
Add draft 'UseGeneratedDependencyPropertyOnManualPropertyCodeFixer'
Sergio0694 Dec 12, 2024
df50c2f
Add 'CSharpCodeFixTest<,>' type
Sergio0694 Dec 12, 2024
7f57e6c
Add basic code fixer test
Sergio0694 Dec 12, 2024
866492b
Optimize registration for default values
Sergio0694 Dec 12, 2024
26fdeed
Handle more default properties in analyzer
Sergio0694 Dec 12, 2024
bebdcac
Handle even more default properties in analyzer
Sergio0694 Dec 12, 2024
be37f5f
Add more generator unit tests
Sergio0694 Dec 13, 2024
26d8f03
Handle supported default value types
Sergio0694 Dec 13, 2024
a3e4c02
Remove unnecessary using directive
Sergio0694 Dec 13, 2024
d8aee78
Fix handling of numerics, add more projected types
Sergio0694 Dec 13, 2024
a7c5130
Handle projected enums in analyzer, add tests
Sergio0694 Dec 13, 2024
3c250da
Fix some analyzer bugs, add unit tests
Sergio0694 Dec 13, 2024
49786ff
Merge branch 'main' into component/DependencyPropertyGenerator
Arlodotexe Dec 13, 2024
3241ea5
Adjust priority for enum typed constants
Sergio0694 Dec 13, 2024
7d65ea1
Fix handling of defaulted custom structs in analyzer
Sergio0694 Dec 13, 2024
e48226c
Improve formatting for known enum members
Sergio0694 Dec 13, 2024
4d085d3
Improve codegen for property changed callbacks
Sergio0694 Dec 16, 2024
bea72f9
Add incrementality tests
Sergio0694 Dec 18, 2024
0262e78
Remove unnecessary test code
Sergio0694 Dec 18, 2024
c315a6c
Fix some leftovers
Sergio0694 Dec 20, 2024
4873405
Remove unnecessary parentheses
Sergio0694 Dec 20, 2024
fc423c5
Improve code fixer for known enum members
Sergio0694 Dec 20, 2024
b65e899
Add .targets to .NET 9 folders too
Sergio0694 Dec 21, 2024
7524a0c
Simplify code fixer tests
Sergio0694 Dec 25, 2024
b85e1bc
Merge branch 'main' into component/DependencyPropertyGenerator
Sergio0694 Dec 25, 2024
45029c6
Undo temporary hacks and workarounds
Sergio0694 Dec 25, 2024
f888673
Fix tooling submodule pointer
Sergio0694 Dec 25, 2024
079da46
Only build for UWP and WindowsAppSDK
Sergio0694 Dec 25, 2024
cce95d3
Fix TFMs for UWP projects
Sergio0694 Dec 25, 2024
a4ced34
Simplify packed .targets files
Sergio0694 Dec 25, 2024
65fdb63
Disable 'UseUwpTools' property
Sergio0694 Dec 25, 2024
5a89531
Fix filenames for packaged .targets files
Sergio0694 Dec 25, 2024
ea71909
Fix typos in .targets file
Sergio0694 Dec 26, 2024
a2f339c
Fix .targets again, add basic test
Sergio0694 Dec 26, 2024
4e5bc48
Bump tooling pointer
Sergio0694 Dec 26, 2024
191e386
Bump UWP .NET 9 target SDK to 18362
Sergio0694 Dec 26, 2024
9924f77
Add more code fixer tests
Sergio0694 Dec 26, 2024
0c45212
Improve handling of known constants
Sergio0694 Dec 26, 2024
500ed36
Improve number formatting, add fixer tests
Sergio0694 Dec 26, 2024
f4ec6f5
Bump SDK of UWP on .NET 9 to 19041
Sergio0694 Dec 26, 2024
53f3195
Remove Uno workaround, fix TFMs
Sergio0694 Dec 26, 2024
f3613a3
Trivia handling
Youssef1313 Dec 26, 2024
c031808
Use the lambda overload
Youssef1313 Dec 26, 2024
9ae5fba
Tweak WinRT types matching logic
Sergio0694 Dec 27, 2024
74919d5
Fix AppServices build
Sergio0694 Dec 27, 2024
7d5c559
Add more test coverage for XML docs
Sergio0694 Dec 27, 2024
17bc997
Fix handling of EOLs in removed members
Sergio0694 Dec 27, 2024
1dadd4d
Fix EOL handling in more scenarios
Sergio0694 Dec 27, 2024
f456963
Respect accessibility of generated accessors
Sergio0694 Dec 27, 2024
70d5857
Add 'SyntaxTriviaExtensions', code tweaks
Sergio0694 Dec 27, 2024
072c750
Add 'AttributeInfo' type and associated logic
Sergio0694 Dec 27, 2024
9af0344
Gather forwarded attributes for generated fields
Sergio0694 Dec 27, 2024
e56f8ae
Fix attributes generation, add unit tests
Sergio0694 Dec 27, 2024
f75dcd5
Update analyzer for attributes, add unit tests
Sergio0694 Dec 27, 2024
e662f76
Update code fixer, add unit tests
Sergio0694 Dec 27, 2024
e617cf0
Add 'StaticAttributeListTargetOnGeneratedDependencyPropertyDeclaratio…
Sergio0694 Dec 27, 2024
2a5f0fe
Add analyzer for forwarded attributes, and tests
Sergio0694 Dec 27, 2024
81e2ad3
Fix outdated comments
Sergio0694 Dec 27, 2024
8a048a8
Strip trivia from forwarded attributes
Sergio0694 Dec 27, 2024
c36129f
Add 'CSharpSuppressorTest<TSuppressor>'
Sergio0694 Dec 27, 2024
c372d84
Fix diagnostic suppressor, add tests
Sergio0694 Dec 27, 2024
e37d7dc
Add interleaved non-fixable properties, add tests
Sergio0694 Dec 28, 2024
21f3684
Add 'partial' for nested types too, add tests
Sergio0694 Dec 28, 2024
9dc6710
Fix codegen for 'bool' default values
Sergio0694 Dec 28, 2024
109d346
Handle explicit 'null' callbacks in metadata
Sergio0694 Dec 28, 2024
8792538
Handle 'string.Empty' as default value
Sergio0694 Dec 28, 2024
53f7436
Add 'UseFieldDeclarationCorrectlyAnalyzer' and tests
Sergio0694 Dec 28, 2024
59e4306
Add 'UseFieldDeclarationCorrectlyCodeFixer' and tests
Sergio0694 Dec 28, 2024
618e6bb
Add 'UseFieldDeclarationAnalyzer' and tests
Sergio0694 Dec 28, 2024
df3c72d
Add 'UseFieldDeclarationCodeFixer' and tests
Sergio0694 Dec 28, 2024
0d01d5c
Handle nested enum types in code fixer
Sergio0694 Dec 28, 2024
922b016
Don't warn on instance fields and properties
Sergio0694 Dec 28, 2024
dff25ef
Fix nested enum test failure
Youssef1313 Dec 29, 2024
1b607b5
Fix UWP XAML checks for legacy UWP
Sergio0694 Dec 29, 2024
b8a7824
Don't suggest using the generator on C# < 13
Sergio0694 Dec 29, 2024
28ab171
Preserve named constants in code fixer
Sergio0694 Dec 30, 2024
21c11bf
Add 'PropertyType' option to attribute
Sergio0694 Dec 31, 2024
454ae8f
Add unit tests for new option
Sergio0694 Dec 31, 2024
9676746
Add 'IsNullableValueType' extensions, code tweaks
Sergio0694 Dec 31, 2024
ce6246e
Add 'ExplicitPropertyMetadataTypeAnalyzer' and tests
Sergio0694 Dec 31, 2024
e1e1c51
Handle invalid boxing conversions
Sergio0694 Dec 31, 2024
7d0b5d9
Minor code refactoring
Sergio0694 Dec 31, 2024
04a3465
Support 'PropertyType' in code fixer, add tests
Sergio0694 Dec 31, 2024
e8b0b1b
Fix a crash when getting additional locations
Sergio0694 Jan 2, 2025
2ae6c2a
Fix handling of additional locations for code fixer
Sergio0694 Jan 2, 2025
6d8f6e2
Remove workaround for unit test runner
Sergio0694 Jan 2, 2025
761f87c
Add '[MaybeNull]' analyzer test
Sergio0694 Jan 2, 2025
c37fb2f
Tweak 'InvalidPropertyNullableAnnotationAnalyzer'
Sergio0694 Jan 2, 2025
efde5ee
Add new nullable diagnostics to analyzer
Sergio0694 Jan 2, 2025
9aaaaea
Add nullability unit tests, minor bug fixes
Sergio0694 Jan 2, 2025
22363a9
Refine logic to emit nullable diagnostics
Sergio0694 Jan 2, 2025
f2b7206
Handle generic containing types
Sergio0694 Jan 2, 2025
a48bf99
Add new generator unit tests
Sergio0694 Jan 2, 2025
76d17fe
Update code fixer tests, add generic tests
Sergio0694 Jan 2, 2025
8782e14
Handle more explicit 'null'-s in code fixer, add tests
Sergio0694 Jan 2, 2025
e3a8de1
Fix 'WCTDP0010' with type parameters, add tests
Sergio0694 Jan 2, 2025
0bf8edd
Add more unit tests for generic types
Sergio0694 Jan 2, 2025
034d8a4
Fix analyzers not running after generators
Sergio0694 Jan 2, 2025
3f743e8
Update tooling
Sergio0694 Jan 2, 2025
4f0ef21
Merge branch 'main' into component/DependencyPropertyGenerator
Sergio0694 Jan 2, 2025
3ba8643
Fix category of all analyzer diagnostics
Sergio0694 Jan 3, 2025
717d602
Add 'GetNamedArgumentOrAttributeLocation'
Sergio0694 Jan 3, 2025
b631a6c
Minor code refactoring to some extensions
Sergio0694 Jan 3, 2025
5ad460b
Improve locations of some diagnostics
Sergio0694 Jan 3, 2025
ae6c314
Handle covariant default value in code fixer
Sergio0694 Jan 3, 2025
28128de
Add more unit tests for nullability
Sergio0694 Jan 3, 2025
45e7e5b
Disable UWP build tools where unnecessary
Sergio0694 Jan 3, 2025
1cad5f3
Improve some diagnostic messages
Sergio0694 Jan 3, 2025
909923d
Add more analyzer unit tests
Sergio0694 Jan 3, 2025
e3e90d7
Fix some default enum scenarios, add tests
Sergio0694 Jan 3, 2025
e73eb24
Fix an edge case with XAML enum types
Sergio0694 Jan 3, 2025
ccd997c
Handle more codegen cases with explicit types
Sergio0694 Jan 4, 2025
a68ed9f
Fix more code fixer edge case scenarios
Sergio0694 Jan 4, 2025
23acb10
Fix more edge cases in code fixer
Sergio0694 Jan 4, 2025
88662ef
Add supplementary warnings on property fields
Sergio0694 Jan 4, 2025
189bd51
Update analyzer tests, add more tests
Sergio0694 Jan 4, 2025
2f412cc
Update code fixer unit tests
Sergio0694 Jan 4, 2025
544c0e5
Enable diagnostics on orphaned properties, add tests
Sergio0694 Jan 4, 2025
ddce264
Emit 'WCTDP0030' even on misnamed fields
Sergio0694 Jan 4, 2025
2ca8aa8
Add new diagnostics for default values
Sergio0694 Jan 4, 2025
d518919
Improve message formats for some diagnostics
Sergio0694 Jan 4, 2025
ff99c77
Handle 'UnsetValue' in analyzer and code fixer
Sergio0694 Jan 4, 2025
901c106
Fix format message for 'WCTDP0028' diagnostic
Sergio0694 Jan 5, 2025
515a42c
Add more unit tests for 'WCTDP0030' diagnostic
Sergio0694 Jan 5, 2025
d6743ba
Emit diagnostics even if default value is not constant
Sergio0694 Jan 5, 2025
c3ec9cf
Handle name colon in forwarded attribute arguments
Sergio0694 Jan 5, 2025
762665e
Tweak diagnostic error codes
Sergio0694 Jan 5, 2025
f43c0e4
Update the embedded attribute type source file
Sergio0694 Jan 5, 2025
36c1995
Merge branch 'main' into component/DependencyPropertyGenerator
Sergio0694 Jan 5, 2025
c8835d3
Analyze default values even if callback is present
Sergio0694 Jan 6, 2025
f7f3e86
Analyze more cases even with invalid field declarations
Sergio0694 Jan 6, 2025
fd9faea
Fix nullability checks in type parameter constraints
Sergio0694 Jan 6, 2025
9a56de0
Add more nullability test cases
Sergio0694 Jan 6, 2025
1d9ba0a
Support target-typed 'new()' expressions for metadata
Sergio0694 Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,4 @@ jobs:
if: ${{ (env.ENABLE_DIAGNOSTICS == 'true' || env.COREHOST_TRACE != '') && always() }}
with:
name: linux-logs
path: ./**/*.*log
path: ./**/*.*log
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<RootNamespace>CommunityToolkit.AppServices</RootNamespace>
<PackageId>$(PackageIdPrefix).$(ToolkitComponentName)</PackageId>
<HasWindowsUIXaml>false</HasWindowsUIXaml>
<HasWinUI>false</HasWinUI>
<EnableXamlCompilerMismatchedTfmUnoWorkaround>false</EnableXamlCompilerMismatchedTfmUnoWorkaround>

<!-- This library doesn't need XAML support, so we can just target 17763 (also disable the UWP build tools) -->
<UseUwpTools>false</UseUwpTools>
<UwpTargetFrameworks>uap10.0.17763;net8.0-windows10.0.17763.0;net9.0-windows10.0.17763.0;</UwpTargetFrameworks>
</PropertyGroup>

<!-- Sets this up as a toolkit component's source project -->
Expand All @@ -29,7 +35,7 @@
</PropertyGroup>

<!-- Add the Desktop Extension SDK when on UWP-->
<ItemGroup Condition="'$(IsUwp)' == 'true' AND $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<ItemGroup Condition="'$(IsUwp)' == 'true' AND '$(MultiTargetPlatformIdentifier)' != 'windows'">
<SDKReference Include="WindowsDesktop, Version=$(TargetPlatformVersion)">
<Name>Windows Desktop Extensions for the UWP</Name>
</SDKReference>
Expand Down
10 changes: 10 additions & 0 deletions components/DependencyPropertyGenerator/.gitattributes
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

<!-- We're intentionally not always matching the namespace with folder path, to better organize the code -->
<NoWarn>$(NoWarn);IDE0130</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.15.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.DependencyPropertyGenerator.SourceGenerators\CommunityToolkit.DependencyPropertyGenerator.SourceGenerators.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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.Linq;
using System.Threading.Tasks;
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.Text;
using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace CommunityToolkit.GeneratedDependencyProperty;

/// <summary>
/// A code fixer that updates property declarations to be fields instead, for dependency properties.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp)]
[Shared]
public sealed class UseFieldDeclarationCodeFixer : CodeFixProvider
{
/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds { get; } = [DependencyPropertyFieldDeclarationId];

/// <inheritdoc/>
public override FixAllProvider? GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

/// <inheritdoc/>
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic diagnostic = context.Diagnostics[0];
TextSpan diagnosticSpan = context.Span;

SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

// Get the property declaration from the target diagnostic
if (root!.FindNode(diagnosticSpan) is PropertyDeclarationSyntax propertyDeclaration)
{
// We only support this code fix for static properties without modifiers and attributes
if (!IsCodeFixSupportedForPropertyDeclaration(propertyDeclaration))
{
return;
}

// We can now register the code fix to convert the property into a field
context.RegisterCodeFix(
CodeAction.Create(
title: "Declare dependency property as field",
createChangedDocument: token => ConvertDependencyPropertyToFieldDeclaration(context.Document, root, propertyDeclaration),
equivalenceKey: "Declare dependency property as field"),
diagnostic);
}
}

/// <summary>
/// Checks whether the code fixer can be applied to a target property declaration.
/// </summary>
/// <param name="propertyDeclaration">The <see cref="PropertyDeclarationSyntax"/> to update.</param>
/// <returns>Whether the code fixer can be applied to <paramref name="propertyDeclaration"/>.</returns>
private static bool IsCodeFixSupportedForPropertyDeclaration(PropertyDeclarationSyntax propertyDeclaration)
{
// We don't support properties with attributes, as those might not work on fields and need special handling
if (propertyDeclaration.AttributeLists.Count > 0)
{
return false;
}

foreach (SyntaxToken modifier in propertyDeclaration.Modifiers)
{
// Accessibility modifiers are allowed (the property will however become public)
if (SyntaxFacts.IsAccessibilityModifier(modifier.Kind()))
{
continue;
}

// If the property is abstract or an override, or other weird things (which shouldn't really happen), we don't support it
if (modifier.Kind() is SyntaxKind.AbstractKeyword or SyntaxKind.OverrideKeyword or SyntaxKind.PartialKeyword or SyntaxKind.ExternKeyword)
{
return false;
}
}

// Properties with an expression body are supported and will be converted to field initializers
if (propertyDeclaration.ExpressionBody is not null)
{
return true;
}

// The property must have at least an accessor
if (propertyDeclaration.AccessorList is not { Accessors.Count: > 0 } accessorList)
{
return false;
}

// One of the accessors must be a getter
if (!accessorList.Accessors.Any(accessor => accessor.IsKind(SyntaxKind.GetAccessorDeclaration)))
{
return false;
}

return true;
}

/// <summary>
/// Applies the code fix to a target property declaration and returns an updated document.
/// </summary>
/// <param name="document">The original document being fixed.</param>
/// <param name="root">The original tree root belonging to the current document.</param>
/// <param name="propertyDeclaration">The <see cref="PropertyDeclarationSyntax"/> to update.</param>
/// <returns>An updated document with the applied code fix.</returns>
private static async Task<Document> ConvertDependencyPropertyToFieldDeclaration(Document document, SyntaxNode root, PropertyDeclarationSyntax propertyDeclaration)
{
await Task.CompletedTask;

SyntaxEditor syntaxEditor = new(root, document.Project.Solution.Workspace.Services);

syntaxEditor.ReplaceNode(propertyDeclaration, (node, generator) =>
{
// If the property had an initializer, carry that over
ExpressionSyntax? initializerExpression = propertyDeclaration switch
{
{ ExpressionBody.Expression: { } arrowExpression } => arrowExpression,
{ Initializer.Value: { } equalsExpression } => equalsExpression,
_ => null
};

// Create the field declaration and make it 'public static readonly' (same as the other analyzer)
SyntaxNode updatedNode = generator.FieldDeclaration(
name: propertyDeclaration.Identifier.Text,
type: propertyDeclaration.Type,
accessibility: Accessibility.Public,
modifiers: DeclarationModifiers.Static | DeclarationModifiers.ReadOnly,
initializer: initializerExpression);

// Keep the 'new' modifier, if needed
if (propertyDeclaration.Modifiers.Any(SyntaxKind.NewKeyword))
{
updatedNode = generator.WithModifiers(updatedNode, generator.GetModifiers(updatedNode).WithIsNew(true));
}

return updatedNode.WithTriviaFrom(propertyDeclaration);
});

return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot());
}
}
Original file line number Diff line number Diff line change
@@ -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.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Text;
using static CommunityToolkit.GeneratedDependencyProperty.Diagnostics.DiagnosticDescriptors;

namespace CommunityToolkit.GeneratedDependencyProperty;

/// <summary>
/// A code fixer that updates field declarations to ensure they follow the recommended rules for dependency properties.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp)]
[Shared]
public sealed class UseFieldDeclarationCorrectlyCodeFixer : CodeFixProvider
{
/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds { get; } = [IncorrectDependencyPropertyFieldDeclarationId];

/// <inheritdoc/>
public override FixAllProvider? GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

/// <inheritdoc/>
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic diagnostic = context.Diagnostics[0];
TextSpan diagnosticSpan = context.Span;

SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

// Get the the field declaration from the target diagnostic
if (root!.FindNode(diagnosticSpan).FirstAncestorOrSelf<FieldDeclarationSyntax>() is { } fieldDeclaration)
{
// Register the code fix to update the field to be correctly declared
context.RegisterCodeFix(
CodeAction.Create(
title: "Declare dependency property field correctly",
createChangedDocument: token => FixDependencyPropertyFieldDeclaration(context.Document, root, fieldDeclaration),
equivalenceKey: "Declare dependency property field correctly"),
diagnostic);
}
}

/// <summary>
/// Applies the code fix to a target field declaration and returns an updated document.
/// </summary>
/// <param name="document">The original document being fixed.</param>
/// <param name="root">The original tree root belonging to the current document.</param>
/// <param name="fieldDeclaration">The <see cref="FieldDeclarationSyntax"/> to update.</param>
/// <returns>An updated document with the applied code fix.</returns>
private static async Task<Document> FixDependencyPropertyFieldDeclaration(Document document, SyntaxNode root, FieldDeclarationSyntax fieldDeclaration)
{
await Task.CompletedTask;

SyntaxEditor syntaxEditor = new(root, document.Project.Solution.Workspace.Services);

// We use the lambda overload mostly for convenient, so we can easily get a generator to use
syntaxEditor.ReplaceNode(fieldDeclaration, (node, generator) =>
{
// Keep the original node to get the trivia back from it
SyntaxNode originalNode = node;

// Update the field to ensure it's declared as 'public static readonly'
node = generator.WithAccessibility(node, Accessibility.Public);
node = generator.WithModifiers(node, DeclarationModifiers.Static | DeclarationModifiers.ReadOnly);

// If the type is declared as nullable, unwrap it and remove the annotation.
// We need to make sure to carry the space after the element type. When the
// type is nullable, that space is attached to the question mark token.
if (((FieldDeclarationSyntax)node).Declaration is { Type: NullableTypeSyntax { ElementType: { } fieldElementType } nullableType } variableDeclaration)
{
TypeSyntax typeDeclaration = fieldElementType.WithTrailingTrivia(nullableType.QuestionToken.TrailingTrivia);

node = ((FieldDeclarationSyntax)node).WithDeclaration(variableDeclaration.WithType(typeDeclaration));
}

return node.WithTriviaFrom(originalNode);
});

// Create the new document with the single change
return document.WithSyntaxRoot(syntaxEditor.GetChangedRoot());
}
}
Loading
Loading