microsoft/healthcare-shared-components

Public

mirrored from https://github.com/microsoft/healthcare-shared-componentsAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0-1.0.79

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tools/Microsoft.Health.Extensions.BuildTimeCodeGenerator/DelegatingInterfaceImplementationGenerator.cs

169lines · modecode

1// -------------------------------------------------------------------------------------------------
2// Copyright (c) Microsoft Corporation. All rights reserved.
3// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4// -------------------------------------------------------------------------------------------------
5
6using System;
7using System.Collections.Generic;
8using System.Diagnostics.CodeAnalysis;
9using System.Linq;
10using System.Reflection;
11using Microsoft.CodeAnalysis;
12using Microsoft.CodeAnalysis.CSharp;
13using Microsoft.CodeAnalysis.CSharp.Syntax;
14using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
15
16namespace Microsoft.Health.Extensions.BuildTimeCodeGenerator
17{
18 /// <summary>
19 /// Generates a class that implements one or more interfaces, delegating implementation to an inner field.
20 /// Implementations are explicit.
21 /// </summary>
22 internal class DelegatingInterfaceImplementationGenerator : ICodeGenerator
23 {
24 internal const string DeclaringTypeKind = "DeclaringType";
25 private readonly SyntaxTokenList _typeModifiers;
26 private readonly SyntaxTokenList _constructorModifiers;
27 private readonly Type[] _interfacesToImplement;
28 private static readonly IdentifierNameSyntax FieldName = IdentifierName("_inner");
29 private static readonly AttributeListSyntax ExcludeFromCodeCoverageAttributeSyntax = AttributeList(SingletonSeparatedList(Attribute(IdentifierName(typeof(ExcludeFromCodeCoverageAttribute).FullName))));
30
31 public DelegatingInterfaceImplementationGenerator(SyntaxTokenList typeModifiers, SyntaxTokenList constructorModifiers, params Type[] interfacesToImplement)
32 {
33 _typeModifiers = typeModifiers;
34 _constructorModifiers = constructorModifiers;
35 _interfacesToImplement = interfacesToImplement;
36 }
37
38 public (MemberDeclarationSyntax[], UsingDirectiveSyntax[]) Generate(string typeName)
39 {
40 var classDeclarration = ClassDeclaration(typeName)
41 .WithModifiers(_typeModifiers)
42 .WithBaseList(BaseList(SeparatedList(_interfacesToImplement.Select(t => (BaseTypeSyntax)SimpleBaseType(t.ToTypeSyntax())))))
43 .AddMembers(
44 FieldDeclaration(VariableDeclaration(_interfacesToImplement[0].ToTypeSyntax()).AddVariables(VariableDeclarator(FieldName.Identifier))).AddModifiers(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)),
45 GetConstructor(typeName))
46 .AddMembers(GetPropertiesAndMethods().ToArray());
47 return (new MemberDeclarationSyntax[] { classDeclarration }, Array.Empty<UsingDirectiveSyntax>());
48 }
49
50 private ConstructorDeclarationSyntax GetConstructor(string className)
51 {
52 return ConstructorDeclaration(className)
53 .WithModifiers(_constructorModifiers)
54 .AddParameterListParameters(Parameter(Identifier("inner")).WithType(_interfacesToImplement[0].ToTypeSyntax()))
55 .AddBodyStatements(
56 ExpressionStatement(
57 AssignmentExpression(
58 SyntaxKind.SimpleAssignmentExpression,
59 FieldName,
60 BinaryExpression(
61 SyntaxKind.CoalesceExpression,
62 IdentifierName("inner"),
63 ThrowExpression(
64 ObjectCreationExpression(typeof(ArgumentNullException).ToTypeSyntax())
65 .AddArgumentListArguments(Argument(
66 InvocationExpression(
67 IdentifierName("nameof"))
68 .AddArgumentListArguments(Argument(
69 IdentifierName("inner"))))))))));
70 }
71
72 private IEnumerable<MemberDeclarationSyntax> GetPropertiesAndMethods()
73 {
74 for (var interfaceIndex = 0; interfaceIndex < _interfacesToImplement.Length; interfaceIndex++)
75 {
76 var interfaceType = _interfacesToImplement[interfaceIndex];
77
78 var typedFieldName = interfaceIndex == 0 ? (ExpressionSyntax)FieldName : ParenthesizedExpression(CastExpression(interfaceType.ToTypeSyntax(), FieldName));
79 var explicitInterfaceSpecifier = ExplicitInterfaceSpecifier((NameSyntax)interfaceType.ToTypeSyntax());
80
81 foreach (var propertyInfo in interfaceType.GetProperties().OrderBy(p => p.Name, StringComparer.Ordinal))
82 {
83 ParameterInfo[] indexParameters = propertyInfo.GetIndexParameters();
84 MethodInfo getter = propertyInfo.GetGetMethod();
85 MethodInfo setter = propertyInfo.GetSetMethod();
86
87 BasePropertyDeclarationSyntax propertyDeclarationSyntax = (indexParameters.Length == 0
88 ? (BasePropertyDeclarationSyntax)PropertyDeclaration(propertyInfo.PropertyType.ToTypeSyntax(), propertyInfo.Name)
89 : IndexerDeclaration(propertyInfo.PropertyType.ToTypeSyntax())
90 .AddParameterListParameters(indexParameters.Select(p => Parameter(Identifier(p.Name)).WithType(p.ParameterType.ToTypeSyntax())).ToArray()))
91 .WithExplicitInterfaceSpecifier(explicitInterfaceSpecifier)
92 .AddAttributeLists(ExcludeFromCodeCoverageAttributeSyntax)
93 .WithAdditionalAnnotations(new SyntaxAnnotation(DeclaringTypeKind, (getter?.GetBaseDefinition() ?? setter?.GetBaseDefinition())?.DeclaringType.FullName));
94
95 if (getter != null)
96 {
97 propertyDeclarationSyntax = propertyDeclarationSyntax.AddAccessorListAccessors(
98 AccessorDeclaration(
99 SyntaxKind.GetAccessorDeclaration,
100 Block(ReturnStatement(
101 indexParameters.Length == 0
102 ? (ExpressionSyntax)MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, FieldName, IdentifierName(propertyInfo.Name))
103 : ElementAccessExpression(FieldName).AddArgumentListArguments(indexParameters.Select(p => Argument(IdentifierName(p.Name))).ToArray())))));
104 }
105
106 if (setter != null)
107 {
108 ExpressionSyntax assignmentTarget = indexParameters.Length == 0
109 ? (ExpressionSyntax)MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, typedFieldName, IdentifierName(propertyInfo.Name))
110 : ElementAccessExpression(FieldName).AddArgumentListArguments(indexParameters.Select(p => Argument(IdentifierName(p.Name))).ToArray());
111
112 propertyDeclarationSyntax = propertyDeclarationSyntax.AddAccessorListAccessors(
113 AccessorDeclaration(
114 SyntaxKind.SetAccessorDeclaration,
115 Block(ExpressionStatement(AssignmentExpression(
116 SyntaxKind.SimpleAssignmentExpression,
117 assignmentTarget,
118 IdentifierName("value"))))));
119 }
120
121 yield return propertyDeclarationSyntax;
122 }
123
124 IOrderedEnumerable<MethodInfo> orderedMethods = interfaceType
125 .GetMethods()
126 .Except(interfaceType.GetProperties().SelectMany(p => p.GetAccessors()))
127 .OrderBy(m => m.Name, StringComparer.Ordinal)
128 .ThenBy(m => string.Join(", ", m.GetParameters().Select(p => p.ParameterType.FullName)), StringComparer.Ordinal);
129
130 foreach (var methodInfo in orderedMethods)
131 {
132 var method = MethodDeclaration(methodInfo.ReturnType.ToTypeSyntax(), methodInfo.Name)
133 .WithExplicitInterfaceSpecifier(explicitInterfaceSpecifier)
134 .AddParameterListParameters(
135 methodInfo.GetParameters().Select(p =>
136 Parameter(Identifier(p.Name))
137 .WithType(p.ParameterType.ToTypeSyntax())
138 .WithModifiers(p.IsDefined(typeof(ParamArrayAttribute), false) ? TokenList(Token(SyntaxKind.ParamsKeyword)) : TokenList()))
139 .ToArray())
140 .AddAttributeLists(ExcludeFromCodeCoverageAttributeSyntax)
141 .WithBody(Block())
142 .WithAdditionalAnnotations(new SyntaxAnnotation(DeclaringTypeKind, methodInfo.GetBaseDefinition().DeclaringType.FullName));
143
144 if (methodInfo.IsGenericMethod)
145 {
146 method = method.WithTypeParameterList(TypeParameterList(SeparatedList(methodInfo.GetGenericArguments().Select(t => TypeParameter(t.Name)))));
147 }
148
149 var methodName = methodInfo.IsGenericMethod
150 ? GenericName(methodInfo.Name).AddTypeArgumentListArguments(methodInfo.GetGenericArguments().Select(t => t.ToTypeSyntax()).ToArray())
151 : (SimpleNameSyntax)IdentifierName(methodInfo.Name);
152
153 var invocation = InvocationExpression(
154 MemberAccessExpression(
155 SyntaxKind.SimpleMemberAccessExpression,
156 typedFieldName,
157 methodName),
158 ArgumentList(SeparatedList(methodInfo.GetParameters().Select(p => Argument(IdentifierName(p.Name))))));
159
160 var block = Block(methodInfo.ReturnType == typeof(void) ? ExpressionStatement(invocation) : (StatementSyntax)ReturnStatement(invocation));
161
162 method = method.WithBody(block);
163
164 yield return method;
165 }
166 }
167 }
168 }
169}
170