1
+ // Copyright (c) .NET Foundation and Contributors
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ namespace SourceGenerator ;
16
+
17
+ [ Generator ]
18
+ public class InheritedCloneGenerator : ISourceGenerator {
19
+ const string AttributeName = "GenerateClone" ;
20
+
21
+ public void Initialize ( GeneratorInitializationContext context ) { }
22
+
23
+ public void Execute ( GeneratorExecutionContext context ) {
24
+ var compilation = context . Compilation ;
25
+
26
+ var candidates = compilation . FindAnnotatedClass ( AttributeName , false ) ;
27
+
28
+ foreach ( var candidate in candidates ) {
29
+ var semanticModel = compilation . GetSemanticModel ( candidate . SyntaxTree ) ;
30
+ var genericClassSymbol = semanticModel . GetDeclaredSymbol ( candidate ) ;
31
+ if ( genericClassSymbol == null ) continue ;
32
+
33
+ // Get the method name from the attribute Name argument
34
+ var attributeData = genericClassSymbol . GetAttributes ( ) . FirstOrDefault ( a => a . AttributeClass ? . Name == $ "{ AttributeName } Attribute") ;
35
+ var methodName = ( string ) attributeData . NamedArguments . FirstOrDefault ( arg => arg . Key == "Name" ) . Value . Value ;
36
+
37
+ // Get the generic argument type where properties need to be copied from
38
+ var attributeSyntax = candidate . AttributeLists
39
+ . SelectMany ( l => l . Attributes )
40
+ . FirstOrDefault ( a => a . Name . ToString ( ) . StartsWith ( AttributeName ) ) ;
41
+ if ( attributeSyntax == null ) continue ; // This should never happen
42
+
43
+ var typeArgumentSyntax = ( ( GenericNameSyntax ) attributeSyntax . Name ) . TypeArgumentList . Arguments [ 0 ] ;
44
+ var typeSymbol = ( INamedTypeSymbol ) semanticModel . GetSymbolInfo ( typeArgumentSyntax ) . Symbol ;
45
+
46
+ var code = GenerateMethod ( candidate , genericClassSymbol , typeSymbol , methodName ) ;
47
+ context . AddSource ( $ "{ genericClassSymbol . Name } .Clone.g.cs", SourceText . From ( code , Encoding . UTF8 ) ) ;
48
+ }
49
+ }
50
+
51
+ static string GenerateMethod (
52
+ TypeDeclarationSyntax classToExtendSyntax ,
53
+ INamedTypeSymbol classToExtendSymbol ,
54
+ INamedTypeSymbol classToClone ,
55
+ string methodName
56
+ ) {
57
+ var namespaceName = classToExtendSymbol . ContainingNamespace . ToDisplayString ( ) ;
58
+ var className = classToExtendSyntax . Identifier . Text ;
59
+ var genericTypeParameters = string . Join ( ", " , classToExtendSymbol . TypeParameters . Select ( tp => tp . Name ) ) ;
60
+ var classDeclaration = classToExtendSymbol . TypeParameters . Length > 0 ? $ "{ className } <{ genericTypeParameters } >" : className ;
61
+
62
+ var all = classToClone . GetBaseTypesAndThis ( ) ;
63
+ var props = all . SelectMany ( x => x . GetMembers ( ) . OfType < IPropertySymbol > ( ) ) . ToArray ( ) ;
64
+ var usings = classToExtendSyntax . SyntaxTree . GetCompilationUnitRoot ( ) . Usings . Select ( u => u . ToString ( ) ) ;
65
+
66
+ var constructorParams = classToExtendSymbol . Constructors . First ( ) . Parameters . ToArray ( ) ;
67
+ var constructorArgs = string . Join ( ", " , constructorParams . Select ( p => $ "original.{ GetPropertyName ( p . Name , props ) } ") ) ;
68
+ var constructorParamNames = constructorParams . Select ( p => p . Name ) . ToArray ( ) ;
69
+
70
+ var properties = props
71
+ // ReSharper disable once PossibleUnintendedLinearSearchInSet
72
+ . Where ( prop => ! constructorParamNames . Contains ( prop . Name , StringComparer . OrdinalIgnoreCase ) && prop . SetMethod != null )
73
+ . Select ( prop => $ " { prop . Name } = original.{ prop . Name } ,")
74
+ . ToArray ( ) ;
75
+
76
+ const string template = """
77
+ {Usings}
78
+
79
+ namespace {Namespace};
80
+
81
+ public partial class {ClassDeclaration} {
82
+ public static {ClassDeclaration} {MethodName}({OriginalClassName} original)
83
+ => new {ClassDeclaration}({ConstructorArgs}) {
84
+ {Properties}
85
+ };
86
+ }
87
+ """ ;
88
+
89
+ var code = template
90
+ . Replace ( "{Usings}" , string . Join ( "\n " , usings ) )
91
+ . Replace ( "{Namespace}" , namespaceName )
92
+ . Replace ( "{ClassDeclaration}" , classDeclaration )
93
+ . Replace ( "{OriginalClassName}" , classToClone . Name )
94
+ . Replace ( "{MethodName}" , methodName )
95
+ . Replace ( "{ConstructorArgs}" , constructorArgs )
96
+ . Replace ( "{Properties}" , string . Join ( "\n " , properties ) . TrimEnd ( ',' ) ) ;
97
+
98
+ return code ;
99
+
100
+ static string GetPropertyName ( string parameterName , IPropertySymbol [ ] properties ) {
101
+ var property = properties . FirstOrDefault ( p => string . Equals ( p . Name , parameterName , StringComparison . OrdinalIgnoreCase ) ) ;
102
+ return property ? . Name ?? parameterName ;
103
+ }
104
+ }
105
+ }
0 commit comments