Skip to content

Commit

Permalink
cake-buildGH-2393: Second pass, attempt to more completely support co…
Browse files Browse the repository at this point in the history
…degen

As a follow-on to the initial commit, this attempts to inspect
those parameters decorated with attributes that utilize a more
complex constructor signature, supporting typical constructor arguments
and named arguments.

This attempts to generally mimic the logic exposed in CustomAttributeTypedArgument,
but using the normal Cake code-gen reflection utils to emit compile-safe type names, etc.
  • Loading branch information
kcamp committed Jan 4, 2019
1 parent 0856e60 commit a62e8de
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 2 deletions.
Expand Up @@ -14,13 +14,43 @@ namespace Cake.Core.Tests.Unit.Scripting.CodeGen
{
public sealed class ParameterEmitterTests
{
private enum TestEnum
public enum TestEnum
{
None,
Some,
All
}

[AttributeUsageAttribute(AttributeTargets.Parameter)]
public sealed class TestParameterAttribute : Attribute
{
public TestParameterAttribute(string name)
{
}

public TestParameterAttribute(int number)
{
}

public TestParameterAttribute(TestEnum value)
{
}

public TestParameterAttribute(TestEnum[] values)
{
}

public TestParameterAttribute()
{
}

public string StringProperty { get; set; }

public int Int32Property { get; set; }

public TestEnum EnumProperty { get; set; }
}

private static class ParameterFixture
{
public static void RequiredInt(int arg)
Expand Down Expand Up @@ -376,6 +406,34 @@ public static void OptionalStringWithAttribute([CallerMemberName] string memberN
public static void OptionalInt32WithAttribute([CallerLineNumber] int sourceLineNumber = 0)
{
}

public static void RequiredStringWithCustomAttributeCalledWithInt32CtorParameter([TestParameter(19)] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithStringCtorParameter([TestParameter("test")] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithEnumCtorParameter([TestParameter(TestEnum.All)] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithArrayCtorParameter([TestParameter(new[] { TestEnum.All, TestEnum.Some })] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithInt32NamedArgument([TestParameter(Int32Property = 19)] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithStringNamedArgument([TestParameter(StringProperty = "test")] string value)
{
}

public static void RequiredStringWithCustomAttributeCalledWithEnumNamedArgument([TestParameter(EnumProperty = TestEnum.All)] string value)
{
}
}

[Theory]
Expand Down Expand Up @@ -467,6 +525,13 @@ public static void OptionalInt32WithAttribute([CallerLineNumber] int sourceLineN
[InlineData("OutputParameterInt32", "out System.Int32 arg")]
[InlineData("OptionalStringWithAttribute", "[System.Runtime.CompilerServices.CallerMemberName] System.String memberName = \"\"")]
[InlineData("OptionalInt32WithAttribute", "[System.Runtime.CompilerServices.CallerLineNumber] System.Int32 sourceLineNumber = (System.Int32)0")]
[InlineData("RequiredStringWithCustomAttributeCalledWithInt32CtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter((System.Int32)19)] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithStringCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(\"test\")] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithEnumCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter((Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2)] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithArrayCtorParameter", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(new Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum[2] { (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2, (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)1 })] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithInt32NamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(Int32Property = (System.Int32)19)] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithStringNamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(StringProperty = \"test\")] System.String value")]
[InlineData("RequiredStringWithCustomAttributeCalledWithEnumNamedArgument", "[Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestParameter(EnumProperty = (Cake.Core.Tests.Unit.Scripting.CodeGen.ParameterEmitterTests.TestEnum)2)] System.String value")]
public void Should_Return_Correct_Generated_Code_For_Method_Parameters(string methodName, string expected)
{
// Given
Expand Down
73 changes: 72 additions & 1 deletion src/Cake.Core/Scripting/CodeGen/ParameterEmitter.cs
Expand Up @@ -50,7 +50,8 @@ private static IEnumerable<string> BuildParameterTokens(ParameterInfo parameter,
{
typeof(OptionalAttribute),
typeof(OutAttribute),
typeof(ParamArrayAttribute)
typeof(ParamArrayAttribute),
typeof(DecimalConstantAttribute)
};

foreach (var item in customAttrs.Where(p => !exclusions.Contains(p.AttributeType)))
Expand All @@ -63,8 +64,30 @@ private static IEnumerable<string> BuildParameterTokens(ParameterInfo parameter,

if (item.ConstructorArguments.Count < 1 && item.NamedArguments.Count < 1)
{
// basic case, empty constructor, just emit the decoration
yield return $"[{attributeType}] ";
}
else
{
// has ctor or named parameters. we'll need to enumerate those options,
// keeping in mind that we have to normalize the type names where it's appropriate.
yield return $"[{attributeType}(";
if (item.ConstructorArguments.Count > 0)
{
yield return string.Join(", ", item.ConstructorArguments.Select(NormalizeCustomAttributeTypedArgument));
}

if (item.NamedArguments.Count > 0)
{
if (item.ConstructorArguments.Count > 0)
{
yield return ", ";
}

yield return string.Join(", ", item.NamedArguments.Select(x => $"{x.MemberName} = {NormalizeCustomAttributeTypedArgument(x.TypedValue)}"));
}
yield return ")] ";
}
}
}

Expand All @@ -91,6 +114,54 @@ private static IEnumerable<string> BuildParameterTokens(ParameterInfo parameter,
}
}

private static string NormalizeCustomAttributeTypedArgument(CustomAttributeTypedArgument arg)
{
if (arg.ArgumentType == null)
{
return arg.ToString();
}

var normalizedTypeName = arg.ArgumentType.GetFullName();

if (arg.ArgumentType.IsEnum)
{
// casting the value to int in the case of an enum
// solves the same issue as the same workaround solves in
// BuildDefaultParameterValueToken, below, on linux/mac
return $"({normalizedTypeName}){(int)arg.Value}";
}

if (arg.Value == null)
{
return $"({normalizedTypeName})null";
}

if (arg.ArgumentType == typeof(string))
{
return $"\"{arg.Value}\"";
}

if (arg.ArgumentType == typeof(char))
{
return $"'{arg.Value}'";
}

if (arg.ArgumentType == typeof(Type))
{
return $"typeof({normalizedTypeName})";
}

if (!arg.ArgumentType.IsArray)
{
return $"({normalizedTypeName}){arg.Value}";
}

// if it's an array, compose those tokens.
var argList = arg.Value as IList<CustomAttributeTypedArgument>;
var arrayType = arg.ArgumentType.GetElementType().GetFullName();
return $"new {arrayType}[{argList.Count}] {{ {string.Join(", ", argList.Select(NormalizeCustomAttributeTypedArgument))} }}";
}

private static string BuildDefaultParameterValueToken(ParameterInfo parameter)
{
var type = parameter.ParameterType;
Expand Down

0 comments on commit a62e8de

Please sign in to comment.