Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,33 @@ protected override string BuildNamespace() => string.IsNullOrEmpty(_inputType?.N

protected static string RemoveUnderscores(string name) => name.Replace("_", string.Empty);

private protected static string GetBackCompatibleName(
private HashSet<string>? _customMemberNames;
private HashSet<string> CustomMemberNames => _customMemberNames ??= new HashSet<string>(
Comment thread
jorgerangel-msft marked this conversation as resolved.
GetCustomMemberNames(),
StringComparer.OrdinalIgnoreCase);

private IEnumerable<string> GetCustomMemberNames()
{
if (CustomCodeView is null)
{
return [];
}

return IsExtensible
? CustomCodeView.Properties.Select(p => p.Name)
: CustomCodeView.Fields.Select(f => f.Name);
}

private protected string GetBackCompatibleName(
Comment thread
jorgerangel-msft marked this conversation as resolved.
string generatedName,
IReadOnlyList<string> generatedNames,
IReadOnlyList<string> lastContractNames)
{
if (lastContractNames.Count == 0)
{
return generatedName;
}

if (lastContractNames.Any(n => n.Equals(generatedName, StringComparison.OrdinalIgnoreCase)))
{
return generatedName;
Expand All @@ -90,9 +112,20 @@ private protected static string GetBackCompatibleName(
.Take(2)
.ToArray();

return matchingCurrentNames.Length == 1 && matchingLastContractNames.Length == 1
var backCompatName = matchingCurrentNames.Length == 1 && matchingLastContractNames.Length == 1
? matchingLastContractNames[0]
: generatedName;

// If restoring the underscore-preserved name would collide with a member that already
// exists in custom code, keep the generated name so the custom member is preserved
// rather than duplicated or removed.
if (!backCompatName.Equals(generatedName, StringComparison.Ordinal)
&& CustomMemberNames.Contains(backCompatName))
{
return generatedName;
}

return backCompatName;
}

protected override bool GetIsEnum() => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,33 @@ await MockHelpers.LoadMockGeneratorAsync(
Assert.AreEqual("Other", enumType.Properties[1].Name);
}

// Verifies that when custom code already implements the underscore-named member (e.g. the
// user manually re-added 'Existing_Value' pointing at the generated 'ExistingValue'), the
// underscore back-compat restoration does NOT rename the generated member. Otherwise the
// generated member would collide with (and effectively remove) the custom implementation.
[Test]
public async Task BackCompat_ExtensibleEnumCustomCodeTakesPrecedenceOverPreservedUnderscores()
{
await MockHelpers.LoadMockGeneratorAsync(
createCSharpTypeCore: (inputType) => typeof(string),
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"),
lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"));

var input = InputFactory.StringEnum("mockInputEnum", [
("ExistingValue", "existing"),
("Other", "other"),
], isExtensible: true);

var enumType = EnumProvider.Create(input);
Assert.IsNotNull(enumType.CustomCodeView);
Assert.IsTrue(enumType.CustomCodeView!.Properties.Any(p => p.Name == "Existing_Value"));

// The generated member keeps its non-underscore name so the custom 'Existing_Value'
Comment thread
jorgerangel-msft marked this conversation as resolved.
// member (which references the generated 'ExistingValue') is preserved.
var content = new TypeProviderWriter(enumType).Write().Content;
Assert.AreEqual(Helpers.GetExpectedFromFile(), content);
}

// Verifies that back-compat does NOT re-introduce enum values that have been suppressed
// via [CodeGenSuppress] or that already exist in user-provided custom code. Without
// filtering in ProcessTypeForBackCompatibility, the back-compat code would rebuild the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#nullable disable

namespace Sample.Models
{
public readonly partial struct MockInputEnum
{
public static MockInputEnum Existing_Value { get; } = ExistingValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#nullable disable

namespace Sample.Models
{
public readonly partial struct MockInputEnum
{
public static MockInputEnum Existing_Value { get; }
public static MockInputEnum Other { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// <auto-generated/>

#nullable disable

using System;
using System.ComponentModel;
using Sample;

namespace Sample.Models
{
public readonly partial struct MockInputEnum : global::System.IEquatable<global::Sample.Models.MockInputEnum>
{
private readonly string _value;
private const string ExistingValueValue = "existing";
private const string OtherValue = "other";

public MockInputEnum(string value)
{
global::Sample.Argument.AssertNotNull(value, nameof(value));

_value = value;
}

public static global::Sample.Models.MockInputEnum ExistingValue { get; } = new global::Sample.Models.MockInputEnum(ExistingValueValue);

public static global::Sample.Models.MockInputEnum Other { get; } = new global::Sample.Models.MockInputEnum(OtherValue);

public static bool operator ==(global::Sample.Models.MockInputEnum left, global::Sample.Models.MockInputEnum right) => left.Equals(right);

public static bool operator !=(global::Sample.Models.MockInputEnum left, global::Sample.Models.MockInputEnum right) => !left.Equals(right);

public static implicit operator global::Sample.Models.MockInputEnum(string value) => new global::Sample.Models.MockInputEnum(value);

public static implicit operator global::Sample.Models.MockInputEnum?(string value) => (value == null) ? null : new global::Sample.Models.MockInputEnum(value);

[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
public override bool Equals(object obj) => ((obj is global::Sample.Models.MockInputEnum other) && this.Equals(other));

public bool Equals(global::Sample.Models.MockInputEnum other) => string.Equals(_value, other._value, global::System.StringComparison.InvariantCultureIgnoreCase);

[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
public override int GetHashCode() => (_value != null) ? global::System.StringComparer.InvariantCultureIgnoreCase.GetHashCode(_value) : 0;

public override string ToString() => _value;
}
}
Loading