Support customizing property names (#4362)

Fixes https://github.com/microsoft/typespec/issues/4257
This commit is contained in:
JoshLove-msft 2024-09-06 14:35:58 -07:00 коммит произвёл GitHub
Родитель 03d4fca5c0
Коммит 782dca54ed
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
8 изменённых файлов: 127 добавлений и 36 удалений

Просмотреть файл

@ -172,38 +172,41 @@ namespace Microsoft.Generator.CSharp.Providers
continue;
var outputProperty = CodeModelPlugin.Instance.TypeFactory.CreatePropertyProvider(property, this);
if (outputProperty != null)
if (outputProperty is null)
continue;
if (HasCustomProperty(outputProperty))
continue;
if (!property.IsDiscriminator)
{
if (!property.IsDiscriminator)
var derivedProperty = InputDerivedProperties.FirstOrDefault(p => p.Value.ContainsKey(property.Name)).Value?[property.Name];
if (derivedProperty is not null)
{
var derivedProperty = InputDerivedProperties.FirstOrDefault(p => p.Value.ContainsKey(property.Name)).Value?[property.Name];
if (derivedProperty is not null)
if (derivedProperty.Type.Equals(property.Type) && DomainEqual(property, derivedProperty))
{
if (derivedProperty.Type.Equals(property.Type) && DomainEqual(property, derivedProperty))
{
outputProperty.Modifiers |= MethodSignatureModifiers.Virtual;
}
}
var baseProperty = baseProperties.GetValueOrDefault(property.Name);
if (baseProperty is not null)
{
if (baseProperty.Type.Equals(property.Type) && DomainEqual(baseProperty, property))
{
outputProperty.Modifiers |= MethodSignatureModifiers.Override;
}
else
{
outputProperty.Modifiers |= MethodSignatureModifiers.New;
var fieldName = $"_{baseProperty.Name.ToVariableName()}";
outputProperty.Body = new ExpressionPropertyBody(
This.Property(fieldName).NullCoalesce(Default),
outputProperty.Body.HasSetter ? This.Property(fieldName).Assign(Value) : null);
}
outputProperty.Modifiers |= MethodSignatureModifiers.Virtual;
}
}
var baseProperty = baseProperties.GetValueOrDefault(property.Name);
if (baseProperty is not null)
{
if (baseProperty.Type.Equals(property.Type) && DomainEqual(baseProperty, property))
{
outputProperty.Modifiers |= MethodSignatureModifiers.Override;
}
else
{
outputProperty.Modifiers |= MethodSignatureModifiers.New;
var fieldName = $"_{baseProperty.Name.ToVariableName()}";
outputProperty.Body = new ExpressionPropertyBody(
This.Property(fieldName).NullCoalesce(Default),
outputProperty.Body.HasSetter ? This.Property(fieldName).Assign(Value) : null);
}
}
properties.Add(outputProperty);
}
properties.Add(outputProperty);
}
if (AdditionalPropertiesProperty != null)
@ -214,6 +217,14 @@ namespace Microsoft.Generator.CSharp.Providers
return [.. properties];
}
private bool HasCustomProperty(PropertyProvider property)
{
if (CustomCodeView == null)
return false;
return CustomCodeView.PropertyNames.Contains(property.Name);
}
private static bool DomainEqual(InputModelProperty baseProperty, InputModelProperty derivedProperty)
{
if (baseProperty.IsRequired != derivedProperty.IsRequired)

Просмотреть файл

@ -64,7 +64,10 @@ namespace Microsoft.Generator.CSharp.Providers
GetCSharpType(propertySymbol.Type),
propertySymbol.Name,
new AutoPropertyBody(propertySymbol.SetMethod is not null),
this);
this)
{
Attributes = propertySymbol.GetAttributes()
};
properties.Add(propertyProvider);
}
return [.. properties];

Просмотреть файл

@ -2,8 +2,10 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Primitives;
@ -36,6 +38,8 @@ namespace Microsoft.Generator.CSharp.Providers
public TypeProvider EnclosingType { get; }
internal IEnumerable<AttributeData>? Attributes { get; init; }
// for mocking
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
protected PropertyProvider()

Просмотреть файл

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.SourceInput;
using Microsoft.Generator.CSharp.Statements;
namespace Microsoft.Generator.CSharp.Providers
@ -13,6 +14,8 @@ namespace Microsoft.Generator.CSharp.Providers
public abstract class TypeProvider
{
private Lazy<TypeProvider?> _customCodeView;
private HashSet<string>? _propertyNames;
protected TypeProvider()
{
_customCodeView = new(GetCustomCodeView);
@ -122,6 +125,8 @@ namespace Microsoft.Generator.CSharp.Providers
private IReadOnlyList<PropertyProvider>? _properties;
public IReadOnlyList<PropertyProvider> Properties => _properties ??= BuildProperties();
internal HashSet<string> PropertyNames => _propertyNames ??= BuildPropertyNames();
private IReadOnlyList<MethodProvider>? _methods;
public IReadOnlyList<MethodProvider> Methods => _methods ??= BuildMethods();
@ -146,6 +151,23 @@ namespace Microsoft.Generator.CSharp.Providers
protected virtual PropertyProvider[] BuildProperties() => [];
private HashSet<string> BuildPropertyNames()
{
var propertyNames = new HashSet<string>();
foreach (var property in Properties)
{
propertyNames.Add(property.Name);
foreach (var attribute in property.Attributes ?? [])
{
if (CodeGenAttributes.TryGetCodeGenMemberAttributeValue(attribute, out var name))
{
propertyNames.Add(name);
}
}
}
return propertyNames;
}
protected virtual FieldProvider[] BuildFields() => [];
protected virtual CSharpType[] BuildImplements() => [];

Просмотреть файл

@ -10,7 +10,7 @@ using Microsoft.Generator.CSharp.Customization;
namespace Microsoft.Generator.CSharp.SourceInput
{
internal class CodeGenAttributes
internal static class CodeGenAttributes
{
public const string CodeGenSuppressAttributeName = "CodeGenSuppressAttribute";
@ -24,7 +24,7 @@ namespace Microsoft.Generator.CSharp.SourceInput
public const string CodeGenSerializationAttributeName = "CodeGenSerializationAttribute";
public bool TryGetCodeGenMemberAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string name)
public static bool TryGetCodeGenMemberAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string name)
{
name = null;
if (attributeData.AttributeClass?.Name != CodeGenMemberAttributeName)
@ -34,7 +34,7 @@ namespace Microsoft.Generator.CSharp.SourceInput
return name != null;
}
public bool TryGetCodeGenSerializationAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string propertyName, out IReadOnlyList<string>? serializationNames, out string? serializationHook, out string? deserializationHook, out string? bicepSerializationHook)
public static bool TryGetCodeGenSerializationAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string propertyName, out IReadOnlyList<string>? serializationNames, out string? serializationHook, out string? deserializationHook, out string? bicepSerializationHook)
{
propertyName = null;
serializationNames = null;
@ -80,7 +80,7 @@ namespace Microsoft.Generator.CSharp.SourceInput
return propertyName != null && (serializationNames != null || serializationHook != null || deserializationHook != null || bicepSerializationHook != null);
}
public bool TryGetCodeGenModelAttributeValue(AttributeData attributeData, out string[]? usage, out string[]? formats)
public static bool TryGetCodeGenModelAttributeValue(AttributeData attributeData, out string[]? usage, out string[]? formats)
{
usage = null;
formats = null;

Просмотреть файл

@ -16,8 +16,6 @@ namespace Microsoft.Generator.CSharp.SourceInput
{
public class SourceInputModel
{
private readonly CodeGenAttributes _codeGenAttributes;
public Compilation? Customization { get; }
private Lazy<Compilation?> _previousContract;
public Compilation? PreviousContract => _previousContract.Value;
@ -29,8 +27,6 @@ namespace Microsoft.Generator.CSharp.SourceInput
Customization = customization;
_previousContract = new(() => LoadBaselineContract().GetAwaiter().GetResult());
_codeGenAttributes = new CodeGenAttributes();
_nameMap = new(PopulateNameMap);
}

Просмотреть файл

@ -11,7 +11,7 @@ namespace Microsoft.Generator.CSharp.Tests.Providers // the namespace here is cr
public class ModelCustomizationTests
{
// Validates that the property body's setter is correctly set based on the property type
[TestCase]
[Test]
public void TestCustomization_CanChangeModelName()
{
MockHelpers.LoadMockPlugin(customization: Helpers.GetCompilationFromFile());
@ -31,5 +31,33 @@ namespace Microsoft.Generator.CSharp.Tests.Providers // the namespace here is cr
Assert.AreEqual(customCodeView?.Name, modelTypeProvider.Type.Name);
Assert.AreEqual(customCodeView?.Type.Namespace, modelTypeProvider.Type.Namespace);
}
[Test]
public void TestCustomization_CanChangePropertyName()
{
MockHelpers.LoadMockPlugin(customization: Helpers.GetCompilationFromFile());
var props = new[]
{
InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String))
};
var inputModel = InputFactory.Model("mockInputModel", properties: props);
var modelTypeProvider = new ModelProvider(inputModel);
var customCodeView = modelTypeProvider.CustomCodeView;
Assert.IsNotNull(customCodeView);
Assert.AreEqual("MockInputModel", modelTypeProvider.Type.Name);
Assert.AreEqual("Sample.Models", modelTypeProvider.Type.Namespace);
Assert.AreEqual(customCodeView?.Name, modelTypeProvider.Type.Name);
Assert.AreEqual(customCodeView?.Type.Namespace, modelTypeProvider.Type.Namespace);
// the property should be filtered from the model provider
Assert.AreEqual(0, modelTypeProvider.Properties.Count);
// the property should be added to the custom code view
Assert.AreEqual(1, customCodeView!.Properties.Count);
Assert.AreEqual("Prop2", customCodeView.Properties[0].Name);
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
#nullable disable
using System;
namespace Sample
{
// TODO: if we decide to use the public APIs, we do not have to define this attribute here. Tracking: https://github.com/Azure/autorest.csharp/issues/4551
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
internal class CodeGenMemberAttribute : Attribute
{
public CodeGenMemberAttribute() : base(null)
{
}
public CodeGenMemberAttribute(string originalName) : base(originalName)
{
}
}
}
namespace Sample.Models
{
public partial class MockInputModel
{
[CodeGenMember("Prop1")]
public string[] Prop2 { get; set; }
}
}