openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
achandmsft-patch-2

Branches

Tags

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

Clone

HTTPS

Download ZIP

codegen/generator/src/Visitors/VisibilityVisitor.cs

215lines · modecode

1using Microsoft.TypeSpec.Generator.ClientModel;
2using Microsoft.TypeSpec.Generator.Expressions;
3using Microsoft.TypeSpec.Generator.Primitives;
4using Microsoft.TypeSpec.Generator.Providers;
5using Microsoft.TypeSpec.Generator.Statements;
6using System;
7using System.Collections.Generic;
8using System.Linq;
9
10namespace OpenAILibraryPlugin.Visitors;
11
12/// <summary>
13/// This visitor updates the public/internal visibility of constructors, methods, and properties on a type:
14/// - Visibility can be customized via the [CodeGenVisibility] attribute, which will always take precedence
15/// - Default visibility is adjusted based on common patterns
16/// </summary>
17public class VisibilityVisitor : ScmLibraryVisitor
18{
19 protected override TypeProvider? PostVisitType(TypeProvider type)
20 {
21 List<AttributeStatement> visibilityAttributes = GetValidatedCodeGenVisibilityAttributes(type);
22
23 IReadOnlyList<ConstructorProvider> allConstructors =
24 [
25 .. type.Constructors,
26 .. type.SerializationProviders.SelectMany(serializer => serializer.Constructors),
27 ];
28 IReadOnlyList<PropertyProvider> allProperties =
29 [
30 .. type.Properties,
31 .. type.SerializationProviders.SelectMany(serializer => serializer.Properties),
32 ];
33 IReadOnlyList<MethodProvider> allMethods =
34 [
35 .. type.Methods,
36 .. type.SerializationProviders.SelectMany(serializer => serializer.Methods),
37 ];
38
39 foreach (ConstructorProvider constructor in allConstructors)
40 {
41 if (!TryUpdateVisibilityFromAttributes(constructor, visibilityAttributes))
42 {
43 _ = TryUpdateDefaultVisibility(constructor);
44 }
45 }
46
47 foreach (PropertyProvider property in allProperties)
48 {
49 if (!TryUpdateVisibilityFromAttributes(property, visibilityAttributes))
50 {
51 _ = TryUpdateDefaultVisibility(property);
52 }
53 }
54
55 foreach (MethodProvider method in allMethods)
56 {
57 if (!TryUpdateVisibilityFromAttributes(method, visibilityAttributes))
58 {
59 _ = TryUpdateDefaultVisibility(method);
60 }
61 }
62
63 return type;
64 }
65
66 private static List<AttributeStatement> GetValidatedCodeGenVisibilityAttributes(TypeProvider type)
67 {
68 IEnumerable<AttributeStatement> allAttributes =
69 [
70 .. type.Attributes,
71 .. type.CustomCodeView?.Attributes ?? [],
72 .. type.SerializationProviders.SelectMany(serializer => serializer.Attributes),
73 .. type.SerializationProviders.SelectMany(serializer => serializer.CustomCodeView?.Attributes ?? []),
74 ];
75
76 List<AttributeStatement> matchingAttributes = [];
77
78 foreach (AttributeStatement attribute in allAttributes.Where(attribute => attribute.Type.Name == "CodeGenVisibilityAttribute"))
79 {
80 if (attribute.Arguments.Count < 2 || attribute.Arguments[0] is not LiteralExpression)
81 {
82 throw new ArgumentException($"Invalid CodeGenVisibilityAttribute provided for {type.Name}; a target name and visibility specifier are required");
83 }
84 matchingAttributes.Add(attribute);
85 }
86
87 return matchingAttributes;
88 }
89
90 private static bool TryUpdateVisibilityFromAttributes<T>(T target, IEnumerable<AttributeStatement> visibilityAttributes)
91 {
92 (string targetName, IReadOnlyList<ParameterProvider> targetParameters, MethodSignatureModifiers startingModifiers, TypeProvider enclosingType) = target switch
93 {
94 PropertyProvider propertyTarget => (propertyTarget.Name, [], propertyTarget.Modifiers, propertyTarget.EnclosingType),
95 ConstructorProvider constructorTarget => (constructorTarget.EnclosingType.Name, constructorTarget.Signature.Parameters, constructorTarget.Signature.Modifiers, constructorTarget.EnclosingType),
96 MethodProvider methodTarget => (methodTarget.Signature.Name, methodTarget.Signature.Parameters, methodTarget.Signature.Modifiers, methodTarget.EnclosingType),
97 _ => throw new NotImplementedException()
98 };
99
100 IEnumerable<AttributeStatement> allNameMatchedVisibilityAttributes = visibilityAttributes
101 .Where(attribute => attribute.Arguments[0] is LiteralExpression literalAttributeTarget
102 && attribute.Arguments.Count == targetParameters.Count + 2
103 && literalAttributeTarget.Literal?.ToString() == targetName);
104
105 string? visibilityFromAttribute = null;
106
107 foreach (AttributeStatement attribute in allNameMatchedVisibilityAttributes)
108 {
109 bool mismatchFound = false;
110 for (int i = 2; i < attribute.Arguments.Count; i++)
111 {
112 if (attribute.Arguments[i] is not TypeOfExpression typeOfExpression || typeOfExpression.Type.Name != targetParameters[i - 2].Type.Name)
113 {
114 mismatchFound = true;
115 break;
116 }
117 }
118 if (!mismatchFound)
119 {
120 visibilityFromAttribute = attribute.Arguments[1].ToDisplayString();
121 break;
122 }
123 }
124
125 MethodSignatureModifiers? modifierFromAttribute = visibilityFromAttribute switch
126 {
127 null => null,
128 "0" => MethodSignatureModifiers.Internal,
129 "1" => MethodSignatureModifiers.Public,
130 _ => throw new NotImplementedException(),
131 };
132
133 return modifierFromAttribute is not null && AssignModifier(target, modifierFromAttribute.Value) != startingModifiers;
134 }
135
136 private static bool TryUpdateDefaultVisibility<T>(T target)
137 {
138 if (target is PropertyProvider propertyTarget)
139 {
140 if (!propertyTarget.Type.IsPublic && propertyTarget.Modifiers.HasFlag(MethodSignatureModifiers.Public))
141 {
142 // Generated properties reflecting internal types can default to internal visibility.
143 AssignModifier(propertyTarget, MethodSignatureModifiers.Internal);
144 return true;
145 }
146 }
147 else if (target is ConstructorProvider constructorTarget)
148 {
149 if (constructorTarget.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)
150 && constructorTarget.Signature.Parameters.Any(parameter => !parameter.Type.IsPublic))
151 {
152 // By default, public constructors with non-public parameter types should be internal
153 AssignModifier(constructorTarget, MethodSignatureModifiers.Internal);
154 }
155 }
156 else if (target is MethodProvider methodTarget)
157 {
158 // No current default visibility change for methods
159 }
160 else
161 {
162 throw new NotImplementedException();
163 }
164
165 return false;
166 }
167
168 private static MethodSignatureModifiers AssignModifier<T>(T target, MethodSignatureModifiers modifier)
169 {
170 MethodSignatureModifiers GetUpdatedModifiers(MethodSignatureModifiers originalModifiers) => modifier switch
171 {
172 MethodSignatureModifiers.Public => originalModifiers & ~MethodSignatureModifiers.Internal & ~MethodSignatureModifiers.Private | MethodSignatureModifiers.Public,
173 MethodSignatureModifiers.Internal => originalModifiers & ~MethodSignatureModifiers.Public & ~MethodSignatureModifiers.Private | MethodSignatureModifiers.Internal,
174 _ => throw new NotImplementedException()
175 };
176
177 MethodSignatureModifiers updatedModifiers = default;
178
179 if (target is PropertyProvider propertyTarget)
180 {
181 updatedModifiers = GetUpdatedModifiers(propertyTarget.Modifiers);
182 propertyTarget.GetType().GetProperty("Modifiers")?.SetValue(propertyTarget, updatedModifiers);
183
184 // Discriminators being made public should not be settable.
185 if (propertyTarget.IsDiscriminator && modifier == MethodSignatureModifiers.Public && propertyTarget.Body is AutoPropertyBody existingBody)
186 {
187 propertyTarget.Update(body: new AutoPropertyBody(HasSetter: false, MethodSignatureModifiers.None, existingBody.InitializationExpression));
188 }
189 }
190 else if (target is ConstructorSignature constructorSignatureTarget)
191 {
192 updatedModifiers = GetUpdatedModifiers(constructorSignatureTarget.Modifiers);
193 constructorSignatureTarget.Update(modifiers: updatedModifiers);
194 }
195 else if (target is MethodSignature methodSignatureTarget)
196 {
197 updatedModifiers = GetUpdatedModifiers(methodSignatureTarget.Modifiers);
198 methodSignatureTarget.Update(modifiers: updatedModifiers);
199 }
200 else if (target is ConstructorProvider finalConstructorTarget)
201 {
202 return AssignModifier(finalConstructorTarget.Signature, modifier);
203 }
204 else if (target is MethodProvider finalMethodTarget)
205 {
206 return AssignModifier(finalMethodTarget.Signature, modifier);
207 }
208 else
209 {
210 throw new NotImplementedException();
211 }
212
213 return updatedModifiers;
214 }
215}