Skip to content

Commit 00b396a

Browse files
Name templates
1 parent 3744a92 commit 00b396a

File tree

6 files changed

+421
-32
lines changed

6 files changed

+421
-32
lines changed

src/Pure.DI.Core/Components/Api.g.cs

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,7 +1879,30 @@ internal interface IConfiguration
18791879
/// </example>
18801880
/// </summary>
18811881
/// <typeparam name="T">The type of dependency to be bound.</typeparam>
1882-
/// <param name="name">Specifies the name template of the root of the composition. If the value is empty, a private root will be created, which can be used when calling <c>Resolve</c> methods.</param>
1882+
/// <param name="name">
1883+
/// Specifies the name of the root of the composition. If the value is empty, a private root will be created, which can be used when calling <c>Resolve</c> methods.
1884+
/// <para>
1885+
/// The name supports templating:
1886+
/// <list type="table">
1887+
/// <listheader>
1888+
/// <term>Template</term>
1889+
/// <description>Description</description>
1890+
/// </listheader>
1891+
/// <item>
1892+
/// <term>{type}</term>
1893+
/// <description>Will be replaced by the short name of the root type without its namespaces.</description>
1894+
/// </item>
1895+
/// <item>
1896+
/// <term>{TYPE}</term>
1897+
/// <description>Will be replaced with the full name of the root type.</description>
1898+
/// </item>
1899+
/// <item>
1900+
/// <term>{tag}</term>
1901+
/// <description>Will be replaced with the first tag name.</description>
1902+
/// </item>
1903+
/// </list>
1904+
/// </para>
1905+
/// </param>
18831906
/// <param name="kind">The Optional argument specifying the kind for the root of the Composition.</param>
18841907
/// <param name="tags">The optional argument that specifies tags for a particular type of dependency binding. If is is not empty, the first tag is used for the root.</param>
18851908
/// <returns>Reference to the setup continuation chain.</returns>
@@ -2016,7 +2039,30 @@ internal interface IConfiguration
20162039
/// </code>
20172040
/// </example>
20182041
/// </summary>
2019-
/// <param name="name">The argument name template.</param>
2042+
/// <param name="name">
2043+
/// The argument name.
2044+
/// <para>
2045+
/// The name supports templating:
2046+
/// <list type="table">
2047+
/// <listheader>
2048+
/// <term>Template</term>
2049+
/// <description>Description</description>
2050+
/// </listheader>
2051+
/// <item>
2052+
/// <term>{type}</term>
2053+
/// <description>Will be replaced by the short name of the argument type without its namespaces.</description>
2054+
/// </item>
2055+
/// <item>
2056+
/// <term>{TYPE}</term>
2057+
/// <description>Will be replaced with the full name of the argument type.</description>
2058+
/// </item>
2059+
/// <item>
2060+
/// <term>{tag}</term>
2061+
/// <description>Will be replaced with the first tag name.</description>
2062+
/// </item>
2063+
/// </list>
2064+
/// </para>
2065+
/// </param>
20202066
/// <param name="tags">The optional argument that specifies the tags for the argument.</param>
20212067
/// <typeparam name="T">The argument type.</typeparam>
20222068
/// <returns>Reference to the setup continuation chain.</returns>
@@ -2031,7 +2077,30 @@ internal interface IConfiguration
20312077
/// </code>
20322078
/// </example>
20332079
/// </summary>
2034-
/// <param name="name">The argument name tamplate.</param>
2080+
/// <param name="name">
2081+
/// The argument name.
2082+
/// <para>
2083+
/// The name supports templating:
2084+
/// <list type="table">
2085+
/// <listheader>
2086+
/// <term>Template</term>
2087+
/// <description>Description</description>
2088+
/// </listheader>
2089+
/// <item>
2090+
/// <term>{type}</term>
2091+
/// <description>Will be replaced by the short name of the argument type without its namespaces.</description>
2092+
/// </item>
2093+
/// <item>
2094+
/// <term>{TYPE}</term>
2095+
/// <description>Will be replaced with the full name of the argument type.</description>
2096+
/// </item>
2097+
/// <item>
2098+
/// <term>{tag}</term>
2099+
/// <description>Will be replaced with the first tag name.</description>
2100+
/// </item>
2101+
/// </list>
2102+
/// </para>
2103+
/// </param>
20352104
/// <param name="tags">The optional argument that specifies the tags for the argument.</param>
20362105
/// <typeparam name="T">The argument type.</typeparam>
20372106
/// <returns>Reference to the setup continuation chain.</returns>
@@ -2045,8 +2114,37 @@ internal interface IConfiguration
20452114
/// .Root&lt;Service&gt;("MyService");
20462115
/// </code>
20472116
/// </example>
2117+
/// <example>
2118+
/// <code>
2119+
/// DI.Setup("Composition")
2120+
/// .Root&lt;Service&gt;("My{type}");
2121+
/// </code>
2122+
/// </example>
20482123
/// </summary>
2049-
/// <param name="name">Specifies the name template of the root of the composition. If the value is empty, a private root will be created, which can be used when calling <c>Resolve</c> methods.</param>
2124+
/// <param name="name">
2125+
/// Specifies the name of the root of the composition. If the value is empty, a private root will be created, which can be used when calling <c>Resolve</c> methods.
2126+
/// <para>
2127+
/// The name supports templating:
2128+
/// <list type="table">
2129+
/// <listheader>
2130+
/// <term>Template</term>
2131+
/// <description>Description</description>
2132+
/// </listheader>
2133+
/// <item>
2134+
/// <term>{type}</term>
2135+
/// <description>Will be replaced by the short name of the root type without its namespaces.</description>
2136+
/// </item>
2137+
/// <item>
2138+
/// <term>{TYPE}</term>
2139+
/// <description>Will be replaced with the full name of the root type.</description>
2140+
/// </item>
2141+
/// <item>
2142+
/// <term>{tag}</term>
2143+
/// <description>Will be replaced with the root tag name.</description>
2144+
/// </item>
2145+
/// </list>
2146+
/// </para>
2147+
/// </param>
20502148
/// <param name="tag">Optional argument specifying the tag for the root of the Composition.</param>
20512149
/// <param name="kind">The Optional argument specifying the kind for the root of the Composition.</param>
20522150
/// <typeparam name="T">The Composition root type.</typeparam>
@@ -2061,8 +2159,33 @@ internal interface IConfiguration
20612159
/// .Roots&lt;IService&gt;();
20622160
/// </code>
20632161
/// </example>
2162+
/// <example>
2163+
/// <code>
2164+
/// DI.Setup("Composition")
2165+
/// .Roots&lt;IService&gt;("Root{type}");
2166+
/// </code>
2167+
/// </example>
20642168
/// </summary>
2065-
/// <param name="name">Specifies the name template of the roots of the composition. If the value is empty, private roots will be created, which can be used when calling <c>Resolve</c> methods.</param>
2169+
/// <param name="name">
2170+
/// Specifies the name of the roots of the composition. If the value is empty, private roots will be created, which can be used when calling <c>Resolve</c> methods.
2171+
/// <para>
2172+
/// The name supports templating:
2173+
/// <list type="table">
2174+
/// <listheader>
2175+
/// <term>Template</term>
2176+
/// <description>Description</description>
2177+
/// </listheader>
2178+
/// <item>
2179+
/// <term>{type}</term>
2180+
/// <description>Will be replaced by the short name of the type without its namespaces.</description>
2181+
/// </item>
2182+
/// <item>
2183+
/// <term>{TYPE}</term>
2184+
/// <description>Will be replaced with the full name of the type.</description>
2185+
/// </item>
2186+
/// </list>
2187+
/// </para>
2188+
/// </param>
20662189
/// <param name="kind">The Optional argument specifying the kind for the root of the Composition.</param>
20672190
/// <typeparam name="T">The Composition root base type.</typeparam>
20682191
/// <returns>Reference to the setup continuation chain.</returns>
@@ -2077,7 +2200,26 @@ internal interface IConfiguration
20772200
/// </code>
20782201
/// </example>
20792202
/// </summary>
2080-
/// <param name="name">Specifies the name template of the builder. The default name tamplate is "BuildUp".</param>
2203+
/// <param name="name">
2204+
/// Specifies the name of the builder. The default name is "BuildUp".
2205+
/// <para>
2206+
/// The name supports templating:
2207+
/// <list type="table">
2208+
/// <listheader>
2209+
/// <term>Template</term>
2210+
/// <description>Description</description>
2211+
/// </listheader>
2212+
/// <item>
2213+
/// <term>{type}</term>
2214+
/// <description>Will be replaced by the short name of the type without its namespaces.</description>
2215+
/// </item>
2216+
/// <item>
2217+
/// <term>{TYPE}</term>
2218+
/// <description>Will be replaced with the full name of the type.</description>
2219+
/// </item>
2220+
/// </list>
2221+
/// </para>
2222+
/// </param>
20812223
/// <param name="kind">The Optional argument specifying the kind for the root of the Composition.</param>
20822224
/// <typeparam name="T">The Composition root type.</typeparam>
20832225
/// <returns>Reference to the setup continuation chain.</returns>
@@ -2088,11 +2230,42 @@ internal interface IConfiguration
20882230
/// <example>
20892231
/// <code>
20902232
/// DI.Setup("Composition")
2091-
/// .Builder&lt;Service&gt;("BuildUpMyService");
2233+
/// .Builders&lt;Service&gt;();
2234+
/// </code>
2235+
/// </example>
2236+
/// <example>
2237+
/// <code>
2238+
/// DI.Setup("Composition")
2239+
/// .Builder&lt;Service&gt;("BuildUp");
2240+
/// </code>
2241+
/// </example>
2242+
/// <example>
2243+
/// <code>
2244+
/// DI.Setup("Composition")
2245+
/// .Builder&lt;Service&gt;("BuildUp{type}");
20922246
/// </code>
20932247
/// </example>
20942248
/// </summary>
2095-
/// <param name="name">Specifies the name template of the builders. The default name tamplate is "BuildUp".</param>
2249+
/// <param name="name">
2250+
/// Specifies the name of the builders. The default name is "BuildUp".
2251+
/// <para>
2252+
/// The name supports templating:
2253+
/// <list type="table">
2254+
/// <listheader>
2255+
/// <term>Template</term>
2256+
/// <description>Description</description>
2257+
/// </listheader>
2258+
/// <item>
2259+
/// <term>{type}</term>
2260+
/// <description>Will be replaced by the short name of the type without its namespaces.</description>
2261+
/// </item>
2262+
/// <item>
2263+
/// <term>{TYPE}</term>
2264+
/// <description>Will be replaced with the full name of the type.</description>
2265+
/// </item>
2266+
/// </list>
2267+
/// </para>
2268+
/// </param>
20962269
/// <param name="kind">The Optional argument specifying the kind for the root of the Composition.</param>
20972270
/// <typeparam name="T">The Composition root base type.</typeparam>
20982271
/// <returns>Reference to the setup continuation chain.</returns>

src/Pure.DI.Core/Core/ApiInvocationProcessor.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -674,9 +674,15 @@ private void VisitArg(
674674
if (genericName.TypeArgumentList.Arguments is [{ } argTypeSyntax]
675675
&& invocation.ArgumentList.Arguments is [{ Expression: { } nameArgExpression }, ..] args)
676676
{
677-
var name = semantic.GetRequiredConstantValue<string>(semanticModel, nameArgExpression).Trim();
677+
var argType = semantic.GetTypeSymbol<INamedTypeSymbol>(semanticModel, argTypeSyntax);
678678
var tags = BuildTags(semanticModel, args.Skip(1));
679-
var argType = semantic.GetTypeSymbol<ITypeSymbol>(semanticModel, argTypeSyntax);
679+
var name = GetName(
680+
nameArgExpression,
681+
semanticModel,
682+
semantic.GetRequiredConstantValue<string>(semanticModel, nameArgExpression),
683+
argType,
684+
tags.IsEmpty ? null : tags[0].Value) ?? "";
685+
680686
metadataVisitor.VisitContract(new MdContract(semanticModel, invocation, argType, ContractKind.Explicit, tags.ToImmutableArray()));
681687
metadataVisitor.VisitArg(new MdArg(semanticModel, argTypeSyntax, argType, name, kind, false, argComments));
682688
}
@@ -935,12 +941,7 @@ private static CompositionName CreateCompositionName(
935941
return nameTemplate;
936942
}
937943

938-
var name = nameFormatter.Format(semanticModel, nameTemplate, type, tag);
939-
if (string.IsNullOrWhiteSpace(name))
940-
{
941-
return name;
942-
}
943-
944+
var name = nameFormatter.Format(semanticModel, nameTemplate!, type, tag);
944945
if (!SyntaxFacts.IsValidIdentifier(name))
945946
{
946947
throw new CompileErrorException($"Invalid identifier \"{name}\".", source.GetLocation(), LogId.ErrorInvalidMetadata);

src/Pure.DI.Core/Core/INameFormatter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
internal interface INameFormatter
44
{
5-
string? Format(
5+
string Format(
66
SemanticModel semanticModel,
7-
string? nameTemplate,
7+
string nameTemplate,
88
INamedTypeSymbol? type,
99
object? tag);
1010
}

src/Pure.DI.Core/Core/NameFormatter.cs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,72 @@ internal class NameFormatter : INameFormatter
1616
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes | SymbolDisplayMiscellaneousOptions.CollapseTupleTypes | SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix
1717
);
1818

19-
public string? Format(
19+
public string Format(
2020
SemanticModel semanticModel,
21-
string? nameTemplate,
21+
string nameTemplate,
2222
INamedTypeSymbol? type,
2323
object? tag)
2424
{
25-
if (string.IsNullOrWhiteSpace(nameTemplate))
25+
if (SyntaxFacts.IsValidIdentifier(nameTemplate))
2626
{
2727
return nameTemplate;
2828
}
2929

30-
if (!nameTemplate.Contains('{'))
31-
{
32-
return nameTemplate;
33-
}
30+
var name = nameTemplate!
31+
.Replace("{type}", type is not null ? type.ToDisplayString(NullableFlowState.NotNull, TypeFormat) : "")
32+
.Replace("{TYPE}", type is not null ? type.ToDisplayString(NullableFlowState.NotNull, FullTypeFormat) : "")
33+
.Replace("{tag}", tag is not null ?tag.ToString() : "");
3434

35-
var name = nameTemplate!.Trim();
36-
if (type is not null)
35+
return ToValidIdentifier(name);
36+
}
37+
38+
internal static string ToValidIdentifier(string text)
39+
{
40+
if (SyntaxFacts.IsValidIdentifier(text))
3741
{
38-
name = name.Replace("{type}", type.ToDisplayString(NullableFlowState.NotNull, TypeFormat));
39-
name = name.Replace("{TYPE}", type.ToDisplayString(NullableFlowState.NotNull, FullTypeFormat)).Replace(".", "");
42+
return text;
4043
}
4144

42-
if (tag is not null)
45+
var chars = text.ToArray();
46+
var size = 0;
47+
for (var i = 0; i < chars.Length; i++)
4348
{
44-
name = name.Replace("{tag}", tag.ToString());
49+
ref var ch = ref chars[i];
50+
if (i == 0)
51+
{
52+
if (!SyntaxFacts.IsIdentifierStartCharacter(ch))
53+
{
54+
chars[size++] = '_';
55+
}
56+
else
57+
{
58+
chars[size++] = ch;
59+
}
60+
}
61+
else
62+
{
63+
if (!SyntaxFacts.IsIdentifierPartCharacter(ch))
64+
{
65+
switch (ch)
66+
{
67+
case '.':
68+
case '`':
69+
case ' ':
70+
case ':':
71+
break;
72+
73+
default:
74+
chars[size++] = '_';
75+
break;
76+
}
77+
}
78+
else
79+
{
80+
chars[size++] = ch;
81+
}
82+
}
4583
}
4684

47-
return name;
85+
return new string(chars, 0, size);
4886
}
4987
}

0 commit comments

Comments
 (0)