88using System . Linq ;
99using System . Reflection ;
1010using CommunityToolkit . Mvvm . SourceGenerators . Extensions ;
11+ using CommunityToolkit . Mvvm . SourceGenerators . Helpers ;
1112using Microsoft . CodeAnalysis ;
1213using Microsoft . CodeAnalysis . CSharp ;
1314using Microsoft . CodeAnalysis . CSharp . Syntax ;
@@ -23,6 +24,18 @@ partial class TransitiveMembersGenerator<TInfo>
2324 /// </summary>
2425 internal static class Execute
2526 {
27+ /// <summary>
28+ /// Checks whether or not nullability attributes are currently available.
29+ /// </summary>
30+ /// <param name="compilation">The input <see cref="Compilation"/> instance.</param>
31+ /// <returns>Whether or not nullability attributes are currently available.</returns>
32+ public static bool IsNullabilitySupported ( Compilation compilation )
33+ {
34+ return
35+ compilation . HasAccessibleTypeWithMetadataName ( "System.Diagnostics.CodeAnalysis.NotNullAttribute" ) &&
36+ compilation . HasAccessibleTypeWithMetadataName ( "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute" ) ;
37+ }
38+
2639 /// <summary>
2740 /// Loads the source <see cref="ClassDeclarationSyntax"/> instance to get member declarations from.
2841 /// </summary>
@@ -68,8 +81,14 @@ public static void ProcessMemberDeclarations(
6881 AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( generatorType . Assembly . GetName ( ) . Version . ToString ( ) ) ) ) ) ) ) )
6982 . WithLeadingTrivia ( member . GetLeadingTrivia ( ) ) ;
7083
84+ // [DebuggerNonUserCode] is not supported on interfaces, fields and event
85+ if ( member . Kind ( ) is not ( SyntaxKind . InterfaceDeclaration or SyntaxKind . FieldDeclaration or SyntaxKind . EventFieldDeclaration ) )
86+ {
87+ member = member . AddAttributeLists ( AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.DebuggerNonUserCode" ) ) ) ) ) ;
88+ }
89+
7190 // [ExcludeFromCodeCoverage] is not supported on interfaces and fields
72- if ( member . Kind ( ) is not SyntaxKind . InterfaceDeclaration and not SyntaxKind . FieldDeclaration )
91+ if ( member . Kind ( ) is not ( SyntaxKind . InterfaceDeclaration or SyntaxKind . FieldDeclaration ) )
7392 {
7493 member = member . AddAttributeLists ( AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage" ) ) ) ) ) ;
7594 }
@@ -95,5 +114,67 @@ public static void ProcessMemberDeclarations(
95114
96115 nonSealedMemberDeclarations = annotatedMemberDeclarations ;
97116 }
117+
118+ /// <summary>
119+ /// Adjusts the nullability annotations for generated members, dropping attributes if needed.
120+ /// </summary>
121+ /// <param name="memberDeclarations">The input sequence of member declarations to generate.</param>
122+ /// <param name="isNullabilitySupported">Whether nullability attributes are supported.</param>
123+ /// <returns>The updated collection of member declarations to generate.</returns>
124+ public static ImmutableArray < MemberDeclarationSyntax > AdjustMemberDeclarationNullabilityAnnotations (
125+ ImmutableArray < MemberDeclarationSyntax > memberDeclarations ,
126+ bool isNullabilitySupported )
127+ {
128+ // If nullability attributes are supported, there is nothing else to do
129+ if ( isNullabilitySupported )
130+ {
131+ return memberDeclarations ;
132+ }
133+
134+ using ImmutableArrayBuilder < MemberDeclarationSyntax > builder = ImmutableArrayBuilder < MemberDeclarationSyntax > . Rent ( ) ;
135+
136+ NullabilityAdjustmentSyntaxRewriter syntaxRewriter = new ( ) ;
137+
138+ // Iterate over all members and adjust the method declarations, if needed
139+ foreach ( MemberDeclarationSyntax memberDeclaration in memberDeclarations )
140+ {
141+ if ( memberDeclaration is MethodDeclarationSyntax methodDeclaration )
142+ {
143+ builder . Add ( ( MethodDeclarationSyntax ) syntaxRewriter . Visit ( methodDeclaration ) ) ;
144+ }
145+ else
146+ {
147+ builder . Add ( memberDeclaration ) ;
148+ }
149+ }
150+
151+ return builder . ToImmutable ( ) ;
152+ }
153+
154+ /// <summary>
155+ /// A custom syntax rewriter that removes nullability attributes from method parameters.
156+ /// </summary>
157+ private sealed class NullabilityAdjustmentSyntaxRewriter : CSharpSyntaxRewriter
158+ {
159+ /// <inheritdoc/>
160+ public override SyntaxNode ? VisitParameter ( ParameterSyntax node )
161+ {
162+ SyntaxNode ? updatedNode = base . VisitParameter ( node ) ;
163+
164+ // If the node is a parameter node with a single attribute being either [NotNull] or [NotNullIfNotNull], drop it.
165+ // This expression will match all parameters with the following format:
166+ //
167+ // ([global::<NAMESPACE>.<ATTRIBUTE_NAME>] <TYPE> <PARAMETER_NAME>)
168+ //
169+ // Where <ATTRIBUTE_NAME> is either "NotNull" or "NotNullIfNotNull". This relies on parameters following this structure
170+ // for nullability annotations, but that is fine in this context given the only source files are the embedded ones.
171+ if ( updatedNode is ParameterSyntax { AttributeLists : [ { Attributes : [ { Name : QualifiedNameSyntax { Right . Identifier . Text : "NotNull" or "NotNullIfNotNull" } } ] } ] } parameterNode )
172+ {
173+ return parameterNode . WithAttributeLists ( default ) ;
174+ }
175+
176+ return updatedNode ;
177+ }
178+ }
98179 }
99180}
0 commit comments