-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathImplementCloneableInterfaceGenerator.cs
162 lines (145 loc) · 7.68 KB
/
ImplementCloneableInterfaceGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright (c) Michael W. Powell. All rights reserved.
// Licensed under the GPLv3 license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
namespace Code.Generation.Roslyn.Generators
{
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static SyntaxFactory;
using static SyntaxKind;
/// <summary>
/// This Generator implements the fairly innocuous <see cref="ICloneable"/> pattern for
/// the decorated class. This is a fairly non-trivial, fairly common, want to do from time
/// to time. There is no reason why a <see cref="ICodeGenerator"/> could not do this mundane
/// work for the subscriber. We could potentially also demonstrate the use of Attribute
/// Parameters, especially to drive the generation, or non-generation, of certain involved
/// elements, such as the copy constructor, the Initialize method, whether to invoke a base
/// copy constructor, and so on.
/// </summary>
/// <inheritdoc />
public class ImplementCloneableInterfaceGenerator : DocumentCodeGenerator
{
/// <summary>
/// Public Constructor.
/// </summary>
/// <param name="attributeData"></param>
/// <inheritdoc />
public ImplementCloneableInterfaceGenerator(AttributeData attributeData) : base(attributeData) { }
private static SyntaxToken EndOfStatementToken => Token(SemicolonToken);
private static SyntaxToken ObjectKeywordToken => Token(ObjectKeyword);
private static SyntaxToken PrivateKeywordToken => Token(PrivateKeyword);
private static SyntaxToken PublicKeywordToken => Token(PublicKeyword);
private static SyntaxToken VoidKeywordToken => Token(VoidKeyword);
/// <summary>
/// Returns the <see cref="UsingDirectiveSyntax"/> for the <typeparamref name="T"/>
/// <see cref="Type.Namespace"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <see cref="Type.Namespace"/>
private static UsingDirectiveSyntax GetUsingNamespaceDirective<T>()
=> UsingDirective(IdentifierName(typeof(T).Namespace));
private static MemberDeclarationSyntax GetICloneableCloneMethodDecl(string className)
=> MethodDeclaration(PredefinedType(ObjectKeywordToken), Identifier(nameof(ICloneable.Clone)))
.WithModifiers(TokenList(PublicKeywordToken))
.WithExpressionBody(ArrowExpressionClause(
ObjectCreationExpression(IdentifierName(className))
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(ThisExpression())
)))
)).WithSemicolonToken(EndOfStatementToken);
// TODO: TBD: we will default to this for now... but this should be conditional whether the method exists...
// TODO: TBD: possibly capture attribute parameters: i.e. generate initialize member, invoke base ctor, copy ctor accessibility, etc
/// <summary>
///
/// </summary>
/// <param name="methodName"></param>
/// <param name="other">The Type of the Other parameter.</param>
/// <returns></returns>
private static MemberDeclarationSyntax GetInitializeMethodDecl(string methodName, string other)
=> MethodDeclaration(PredefinedType(VoidKeywordToken), Identifier(methodName))
.WithModifiers(TokenList(PrivateKeywordToken))
.WithParameterList(ParameterList(
SingletonSeparatedList(
Parameter(Identifier(nameof(other))).WithType(IdentifierName(other)))
))
.WithBody(Block());
/// <summary>
///
/// </summary>
/// <param name="other">The Type of the Other parameter.
/// As a Copy Constructor, also informs the name itself.</param>
/// <param name="invocationName"></param>
/// <returns></returns>
private static MemberDeclarationSyntax GetCopyCtorDecl(string other, string invocationName)
=> ConstructorDeclaration(Identifier(other))
.WithModifiers(TokenList(PrivateKeywordToken))
.WithParameterList(ParameterList(SingletonSeparatedList(
Parameter(Identifier(nameof(other)))
.WithType(IdentifierName(other))
)))
.WithBody(Block(SingletonList(ExpressionStatement(
InvocationExpression(IdentifierName(invocationName))
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(IdentifierName(nameof(other)))
)))
))));
/// <summary>
/// Now, this is a fairly trivial, if a bit naive Code Generation implementation.
/// We are solely interested in making sure that fundamental building blocks of
/// Code Generation, such as inserting a Leading Preamble Text, are taking place
/// successfully.
/// </summary>
/// <inheritdoc />
public override Task GenerateAsync(DocumentTransformationContext context, IProgress<Diagnostic> progress
, CancellationToken cancellationToken)
{
IEnumerable<CodeGeneratorDescriptor> Generate()
{
var namespaceDecl = context.SourceCompilationUnit.DescendantNodesAndSelf().OfType<NamespaceDeclarationSyntax>().Single();
var classDecl = context.SourceCompilationUnit.DescendantNodesAndSelf().OfType<ClassDeclarationSyntax>().Single();
// ReSharper disable once InconsistentNaming
const string Initialize = nameof(Initialize);
var other = $"{classDecl.Identifier}";
yield return new CodeGeneratorDescriptor
{
CompilationUnits =
{
CompilationUnit()
.WithUsings(SingletonList(GetUsingNamespaceDirective<ICloneable>()))
.WithMembers(SingletonList<MemberDeclarationSyntax>(
NamespaceDeclaration(namespaceDecl.Name)
.WithMembers(SingletonList<MemberDeclarationSyntax>(
ClassDeclaration(classDecl.Identifier)
.WithModifiers(classDecl.Modifiers)
.WithBaseList(BaseList(
SingletonSeparatedList<BaseTypeSyntax>(
SimpleBaseType(IdentifierName(nameof(ICloneable)))
)
))
.WithMembers(List(new[]
{
GetCopyCtorDecl(other, Initialize),
GetInitializeMethodDecl(Initialize, other),
GetICloneableCloneMethodDecl(other)
}))
))
))
}
};
}
void RunGenerate()
{
foreach (var d in Generate())
{
Descriptors.Add(d);
}
}
return Task.Run(RunGenerate, cancellationToken);
}
}
}