Skip to content

Commit f2316eb

Browse files
Refactor: Extract common argument mapping logic into a generic method and add comprehensive tests for argument handling scenarios
1 parent d15979b commit f2316eb

2 files changed

Lines changed: 190 additions & 54 deletions

File tree

src/Pure.DI.Core/Core/Arguments.cs

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,21 @@ sealed class Arguments : IArguments
66
{
77
public AttributeArgumentSyntax?[] GetArgs(
88
AttributeArgumentListSyntax argumentListSyntax,
9-
params string[] colons)
10-
{
11-
var args = new AttributeArgumentSyntax?[colons.Length];
12-
for (var argIndex = 0; argIndex < argumentListSyntax.Arguments.Count; argIndex++)
13-
{
14-
var arg = argumentListSyntax.Arguments[argIndex];
15-
if (arg.NameColon?.Name.Identifier.Text is {} colonName)
16-
{
17-
for (var colonIndex = 0; colonIndex < colons.Length; colonIndex++)
18-
{
19-
if (colons[colonIndex] == colonName)
20-
{
21-
args[colonIndex] = arg;
22-
}
23-
}
24-
}
25-
else
26-
{
27-
if (argIndex < args.Length)
28-
{
29-
args[argIndex] = arg;
30-
}
31-
}
32-
}
33-
34-
return args;
35-
}
9+
params string[] colons) =>
10+
GetArgs(
11+
argumentListSyntax.Arguments.Count,
12+
i => argumentListSyntax.Arguments[i],
13+
arg => arg.NameColon?.Name.Identifier.Text,
14+
colons);
3615

3716
public ArgumentSyntax?[] GetArgs(
3817
BaseArgumentListSyntax argumentListSyntax,
39-
params string[] colons)
40-
{
41-
var args = new ArgumentSyntax[colons.Length];
42-
for (var argIndex = 0; argIndex < argumentListSyntax.Arguments.Count; argIndex++)
43-
{
44-
var arg = argumentListSyntax.Arguments[argIndex];
45-
if (arg.NameColon?.Name.Identifier.Text is {} colonName)
46-
{
47-
for (var colonIndex = 0; colonIndex < colons.Length; colonIndex++)
48-
{
49-
if (colons[colonIndex] == colonName)
50-
{
51-
args[colonIndex] = arg;
52-
}
53-
}
54-
}
55-
else
56-
{
57-
if (argIndex < args.Length)
58-
{
59-
args[argIndex] = arg;
60-
}
61-
}
62-
}
63-
64-
return args;
65-
}
18+
params string[] colons) =>
19+
GetArgs(
20+
argumentListSyntax.Arguments.Count,
21+
i => argumentListSyntax.Arguments[i],
22+
arg => arg.NameColon?.Name.Identifier.Text,
23+
colons);
6624

6725
public TypedConstant[] GetArgs(
6826
ImmutableArray<TypedConstant> attributeConstructorArguments,
@@ -94,4 +52,37 @@ public TypedConstant[] GetArgs(
9452

9553
return args;
9654
}
55+
56+
private static TArg?[] GetArgs<TArg>(
57+
int argsCount,
58+
Func<int, TArg> argProvider,
59+
Func<TArg, string?> nameProvider,
60+
string[] colons)
61+
where TArg : class
62+
{
63+
var args = new TArg?[colons.Length];
64+
for (var argIndex = 0; argIndex < argsCount; argIndex++)
65+
{
66+
var arg = argProvider(argIndex);
67+
if (nameProvider(arg) is { } colonName)
68+
{
69+
for (var colonIndex = 0; colonIndex < colons.Length; colonIndex++)
70+
{
71+
if (colons[colonIndex] == colonName)
72+
{
73+
args[colonIndex] = arg;
74+
}
75+
}
76+
77+
continue;
78+
}
79+
80+
if (argIndex < args.Length)
81+
{
82+
args[argIndex] = arg;
83+
}
84+
}
85+
86+
return args;
87+
}
9788
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
namespace Pure.DI.Tests;
2+
3+
using Core;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
public class ArgumentsTests
9+
{
10+
[Fact]
11+
public void ShouldMapBaseArgumentsByPosition()
12+
{
13+
// Given
14+
var sut = new Arguments();
15+
var argumentList = ParseInvocationArgumentList("F(1, 2, 3);");
16+
17+
// When
18+
var args = sut.GetArgs(argumentList, "x", "y", "z");
19+
20+
// Then
21+
args[0]!.ToString().ShouldBe("1");
22+
args[1]!.ToString().ShouldBe("2");
23+
args[2]!.ToString().ShouldBe("3");
24+
}
25+
26+
[Fact]
27+
public void ShouldMapBaseArgumentsByName()
28+
{
29+
// Given
30+
var sut = new Arguments();
31+
var argumentList = ParseInvocationArgumentList("F(y: 2, x: 1);");
32+
33+
// When
34+
var args = sut.GetArgs(argumentList, "x", "y");
35+
36+
// Then
37+
args[0]!.ToString().ShouldBe("x: 1");
38+
args[1]!.ToString().ShouldBe("y: 2");
39+
}
40+
41+
[Fact]
42+
public void ShouldMapAttributeArgumentsInMixedMode()
43+
{
44+
// Given
45+
var sut = new Arguments();
46+
var argumentList = ParseAttributeArgumentList("[A(1, y: 2, 3)]");
47+
48+
// When
49+
var args = sut.GetArgs(argumentList, "x", "y", "z");
50+
51+
// Then
52+
args[0]!.ToString().ShouldBe("1");
53+
args[1]!.ToString().ShouldBe("y: 2");
54+
args[2]!.ToString().ShouldBe("3");
55+
}
56+
57+
[Fact]
58+
public void ShouldMapAttributeArgumentsByNameInAnyOrder()
59+
{
60+
// Given
61+
var sut = new Arguments();
62+
var argumentList = ParseAttributeArgumentList("[A(namespaceName: \"Contracts\", asInternal: true, interfaceName: \"IMy\")]");
63+
64+
// When
65+
var args = sut.GetArgs(argumentList, "namespaceName", "interfaceName", "asInternal");
66+
67+
// Then
68+
args[0]!.ToString().ShouldBe("namespaceName: \"Contracts\"");
69+
args[1]!.ToString().ShouldBe("interfaceName: \"IMy\"");
70+
args[2]!.ToString().ShouldBe("asInternal: true");
71+
}
72+
73+
[Fact]
74+
public void ShouldMapTypedConstantArgumentsWithNamedOverride()
75+
{
76+
// Given
77+
var sut = new Arguments();
78+
var attributeData = ParseAttributeData("""
79+
using System;
80+
[AttributeUsage(AttributeTargets.Class)]
81+
public sealed class AAttribute : Attribute
82+
{
83+
public AAttribute(string namespaceName, string interfaceName, bool asInternal) { }
84+
}
85+
[A("Demo", interfaceName: "IMy", asInternal: true)]
86+
public class C { }
87+
""");
88+
89+
// When
90+
var args = sut.GetArgs(
91+
attributeData.ConstructorArguments,
92+
attributeData.NamedArguments,
93+
"namespaceName",
94+
"interfaceName",
95+
"asInternal");
96+
97+
// Then
98+
args[0].Value.ShouldBe("Demo");
99+
args[1].Value.ShouldBe("IMy");
100+
args[2].Value.ShouldBe(true);
101+
}
102+
103+
private static BaseArgumentListSyntax ParseInvocationArgumentList(string invocationStatement)
104+
{
105+
var tree = CSharpSyntaxTree.ParseText($$"""
106+
class C
107+
{
108+
void M()
109+
{
110+
{{invocationStatement}}
111+
}
112+
}
113+
""");
114+
var root = tree.GetCompilationUnitRoot();
115+
return root.DescendantNodes().OfType<InvocationExpressionSyntax>().Single().ArgumentList;
116+
}
117+
118+
private static AttributeArgumentListSyntax ParseAttributeArgumentList(string attributeText)
119+
{
120+
var tree = CSharpSyntaxTree.ParseText($$"""
121+
{{attributeText}}
122+
class C { }
123+
""");
124+
var root = tree.GetCompilationUnitRoot();
125+
return root.DescendantNodes().OfType<AttributeSyntax>().Single().ArgumentList!;
126+
}
127+
128+
private static AttributeData ParseAttributeData(string code)
129+
{
130+
var tree = CSharpSyntaxTree.ParseText(code);
131+
var compilation = CSharpCompilation.Create(
132+
"Tests",
133+
[tree],
134+
[MetadataReference.CreateFromFile(typeof(object).Assembly.Location)],
135+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
136+
var semanticModel = compilation.GetSemanticModel(tree);
137+
var classSymbol = semanticModel.SyntaxTree.GetRoot()
138+
.DescendantNodes()
139+
.OfType<ClassDeclarationSyntax>()
140+
.Single(x => x.Identifier.Text == "C");
141+
return ((INamedTypeSymbol)semanticModel.GetDeclaredSymbol(classSymbol)!)
142+
.GetAttributes()
143+
.Single();
144+
}
145+
}

0 commit comments

Comments
 (0)