Skip to content

Commit

Permalink
Support customizing property names (#4362)
Browse files Browse the repository at this point in the history
Fixes #4257
  • Loading branch information
JoshLove-msft authored Sep 6, 2024
1 parent 03d4fca commit 782dca5
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,38 +172,41 @@ protected override PropertyProvider[] BuildProperties()
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;
}
outputProperty.Modifiers |= MethodSignatureModifiers.Virtual;
}
var baseProperty = baseProperties.GetValueOrDefault(property.Name);
if (baseProperty is not null)
}
var baseProperty = baseProperties.GetValueOrDefault(property.Name);
if (baseProperty is not null)
{
if (baseProperty.Type.Equals(property.Type) && DomainEqual(baseProperty, property))
{
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.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)
Expand All @@ -214,6 +217,14 @@ protected override PropertyProvider[] BuildProperties()
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ protected override PropertyProvider[] BuildProperties()
GetCSharpType(propertySymbol.Type),
propertySymbol.Name,
new AutoPropertyBody(propertySymbol.SetMethod is not null),
this);
this)
{
Attributes = propertySymbol.GetAttributes()
};
properties.Add(propertyProvider);
}
return [.. properties];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,6 +38,8 @@ public class PropertyProvider

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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
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
{
public abstract class TypeProvider
{
private Lazy<TypeProvider?> _customCodeView;
private HashSet<string>? _propertyNames;

protected TypeProvider()
{
_customCodeView = new(GetCustomCodeView);
Expand Down Expand Up @@ -122,6 +125,8 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal()
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();

Expand All @@ -146,6 +151,23 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal()

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() => [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Microsoft.Generator.CSharp.SourceInput
{
internal class CodeGenAttributes
internal static class CodeGenAttributes
{
public const string CodeGenSuppressAttributeName = "CodeGenSuppressAttribute";

Expand All @@ -24,7 +24,7 @@ internal class CodeGenAttributes

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)
Expand All @@ -34,7 +34,7 @@ public bool TryGetCodeGenMemberAttributeValue(AttributeData attributeData, [Mayb
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;
Expand Down Expand Up @@ -80,7 +80,7 @@ public bool TryGetCodeGenSerializationAttributeValue(AttributeData attributeData
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,8 +27,6 @@ public SourceInputModel(Compilation? customization)
Customization = customization;
_previousContract = new(() => LoadBaselineContract().GetAwaiter().GetResult());

_codeGenAttributes = new CodeGenAttributes();

_nameMap = new(PopulateNameMap);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -31,5 +31,33 @@ public void TestCustomization_CanChangeModelName()
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}

0 comments on commit 782dca5

Please sign in to comment.