Skip to content

Commit 9674a2c

Browse files
authored
[SourceGen] Fix for duplicate properties generated (#3227)
1 parent 37f9796 commit 9674a2c

File tree

7 files changed

+280
-15
lines changed

7 files changed

+280
-15
lines changed

sdk/FunctionMetadataLoaderExtension/FunctionMetadataLoaderExtension.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.41" />
14-
<PackageReference Include="Microsoft.Azure.WebJobs.Script.Abstractions" Version="1.0.3-preview" />
14+
<PackageReference Include="Microsoft.Azure.WebJobs.Script.Abstractions" Version="1.0.4-preview" />
1515
</ItemGroup>
1616

1717
</Project>

sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.;
33

44
using System;
@@ -612,7 +612,7 @@ private bool TryCreateBindingDictionary(AttributeData bindingAttrData, string bi
612612

613613
private bool TryGetAttributeProperties(AttributeData attributeData, Location? attribLocation, out IDictionary<string, object?>? attrProperties)
614614
{
615-
attrProperties = new Dictionary<string, object?>();
615+
attrProperties = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
616616

617617
if (attributeData.ConstructorArguments.Any())
618618
{

sdk/Sdk.Generators/Sdk.Generators.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<IncludeBuildOutput>false</IncludeBuildOutput>
1111
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
1212
<MinorProductVersion>3</MinorProductVersion>
13-
<PatchProductVersion>5</PatchProductVersion>
13+
<PatchProductVersion>6</PatchProductVersion>
1414
<IsRoslynComponent>true</IsRoslynComponent>
1515
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
1616
</PropertyGroup>

sdk/release_notes.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
- Build no longer generates `functions.metadata` if source-generated metadata provider is enabled. (#2974)
1010
- Fixing `dotnet run` to work on Windows when core tools is installed from NPM (#3127)
11+
- `Microsoft.Azure.Functions.Worker.Sdk.Generators` bumped to `1.3.6`.
1112

12-
### Microsoft.Azure.Functions.Worker.Sdk.Generators <version>
13+
### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.3.6
1314

14-
- <entry>
15+
- Fix bug that results in duplicate properties recorded for a binding (#3227)
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Reflection;
5+
using System.Threading.Tasks;
6+
using Microsoft.Azure.Functions.Sdk.Generator.Tests;
7+
using Microsoft.Azure.Functions.Worker.Sdk.Generators;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using Xunit;
12+
13+
namespace Microsoft.Azure.Functions.Sdk.Generator.FunctionMetadataProvider.Tests
14+
{
15+
public partial class FunctionMetadataProviderGeneratorTests
16+
{
17+
public class BindingPropertiesParsingTests
18+
{
19+
private readonly Assembly[] _referencedExtensionAssemblies;
20+
21+
public BindingPropertiesParsingTests()
22+
{
23+
var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll");
24+
var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll");
25+
var hostingExtension = typeof(HostBuilder).Assembly;
26+
var diExtension = typeof(DefaultServiceProviderFactory).Assembly;
27+
var hostingAbExtension = typeof(IHost).Assembly;
28+
var diAbExtension = typeof(IServiceCollection).Assembly;
29+
var mcpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Mcp.dll");
30+
var blobExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs.dll");
31+
32+
_referencedExtensionAssemblies = new[]
33+
{
34+
abstractionsExtension,
35+
httpExtension,
36+
hostingExtension,
37+
hostingAbExtension,
38+
diExtension,
39+
diAbExtension,
40+
mcpExtension,
41+
blobExtension
42+
};
43+
}
44+
45+
[Theory]
46+
[InlineData(LanguageVersion.CSharp7_3)]
47+
[InlineData(LanguageVersion.CSharp8)]
48+
[InlineData(LanguageVersion.CSharp9)]
49+
[InlineData(LanguageVersion.CSharp10)]
50+
[InlineData(LanguageVersion.CSharp11)]
51+
[InlineData(LanguageVersion.Latest)]
52+
public async Task BindingPropertiesWithDefaultValueDoeNotCreateDuplicatesWhenSetAsNamedArgument(LanguageVersion languageVersion)
53+
{
54+
string inputCode = """
55+
using System;
56+
using System.Collections.Generic;
57+
using Microsoft.Azure.Functions.Worker;
58+
using Microsoft.Azure.Functions.Worker.Http;
59+
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
60+
61+
namespace MyCompany.Task
62+
{
63+
public static class SaveSnippetFunction
64+
{
65+
[Function(nameof(SaveSnippetFunction))]
66+
[BlobOutput("blobPath")]
67+
public static string SaveSnippet(
68+
[McpToolTrigger("someString", "someString")]
69+
ToolInvocationContext context,
70+
[McpToolProperty("someString", "someString", IsRequired = true)]
71+
string name
72+
)
73+
{
74+
throw new NotImplementedException();
75+
}
76+
}
77+
}
78+
""";
79+
80+
string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs";
81+
string expectedOutput = $$"""
82+
// <auto-generated/>
83+
using System;
84+
using System.Collections.Generic;
85+
using System.Collections.Immutable;
86+
using System.Text.Json;
87+
using System.Threading.Tasks;
88+
using Microsoft.Azure.Functions.Worker;
89+
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
90+
using Microsoft.Extensions.DependencyInjection;
91+
using Microsoft.Extensions.Hosting;
92+
93+
namespace TestProject
94+
{
95+
/// <summary>
96+
/// Custom <see cref="IFunctionMetadataProvider"/> implementation that returns function metadata definitions for the current worker.
97+
/// </summary>
98+
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
99+
{{Constants.GeneratedCodeAttribute}}
100+
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
101+
{
102+
/// <inheritdoc/>
103+
public Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(string directory)
104+
{
105+
var metadataList = new List<IFunctionMetadata>();
106+
var Function0RawBindings = new List<string>();
107+
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""blob"",""direction"":""Out"",""blobPath"":""blobPath""}");
108+
Function0RawBindings.Add(@"{""name"":""context"",""type"":""mcpToolTrigger"",""direction"":""In"",""toolName"":""someString"",""description"":""someString""}");
109+
Function0RawBindings.Add(@"{""name"":""name"",""type"":""mcpToolProperty"",""direction"":""In"",""propertyName"":""someString"",""description"":""someString"",""isRequired"":true,""dataType"":""String""}");
110+
111+
var Function0 = new DefaultFunctionMetadata
112+
{
113+
Language = "dotnet-isolated",
114+
Name = "SaveSnippetFunction",
115+
EntryPoint = "MyCompany.Task.SaveSnippetFunction.SaveSnippet",
116+
RawBindings = Function0RawBindings,
117+
ScriptFile = "TestProject.dll"
118+
};
119+
metadataList.Add(Function0);
120+
121+
return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray());
122+
}
123+
}
124+
125+
/// <summary>
126+
/// Extension methods to enable registration of the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
127+
/// </summary>
128+
{{Constants.GeneratedCodeAttribute}}
129+
public static class WorkerHostBuilderFunctionMetadataProviderExtension
130+
{
131+
///<summary>
132+
/// Adds the GeneratedFunctionMetadataProvider to the service collection.
133+
/// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing.
134+
///</summary>
135+
public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder)
136+
{
137+
builder.ConfigureServices(s =>
138+
{
139+
s.AddSingleton<IFunctionMetadataProvider, GeneratedFunctionMetadataProvider>();
140+
});
141+
return builder;
142+
}
143+
}
144+
}
145+
""";
146+
147+
await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
148+
_referencedExtensionAssemblies,
149+
inputCode,
150+
expectedGeneratedFileName,
151+
expectedOutput,
152+
languageVersion: languageVersion);
153+
}
154+
155+
[Theory]
156+
[InlineData(LanguageVersion.CSharp7_3)]
157+
[InlineData(LanguageVersion.CSharp8)]
158+
[InlineData(LanguageVersion.CSharp9)]
159+
[InlineData(LanguageVersion.CSharp10)]
160+
[InlineData(LanguageVersion.CSharp11)]
161+
[InlineData(LanguageVersion.Latest)]
162+
public async Task BindingPropertyWithDefaultValueIsSet(LanguageVersion languageVersion)
163+
{
164+
string inputCode = """
165+
using System;
166+
using System.Collections.Generic;
167+
using Microsoft.Azure.Functions.Worker;
168+
using Microsoft.Azure.Functions.Worker.Http;
169+
using Microsoft.Azure.Functions.Worker.Extensions.Mcp;
170+
171+
namespace MyCompany.Task
172+
{
173+
public static class SaveSnippetFunction
174+
{
175+
[Function(nameof(SaveSnippetFunction))]
176+
[BlobOutput("blobPath")]
177+
public static string SaveSnippet(
178+
[McpToolTrigger("someString", "someString")]
179+
ToolInvocationContext context,
180+
[McpToolProperty("someString", "someString")]
181+
string name
182+
)
183+
{
184+
throw new NotImplementedException();
185+
}
186+
}
187+
}
188+
""";
189+
190+
string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs";
191+
string expectedOutput = $$"""
192+
// <auto-generated/>
193+
using System;
194+
using System.Collections.Generic;
195+
using System.Collections.Immutable;
196+
using System.Text.Json;
197+
using System.Threading.Tasks;
198+
using Microsoft.Azure.Functions.Worker;
199+
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
200+
using Microsoft.Extensions.DependencyInjection;
201+
using Microsoft.Extensions.Hosting;
202+
203+
namespace TestProject
204+
{
205+
/// <summary>
206+
/// Custom <see cref="IFunctionMetadataProvider"/> implementation that returns function metadata definitions for the current worker.
207+
/// </summary>
208+
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
209+
{{Constants.GeneratedCodeAttribute}}
210+
public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider
211+
{
212+
/// <inheritdoc/>
213+
public Task<ImmutableArray<IFunctionMetadata>> GetFunctionMetadataAsync(string directory)
214+
{
215+
var metadataList = new List<IFunctionMetadata>();
216+
var Function0RawBindings = new List<string>();
217+
Function0RawBindings.Add(@"{""name"":""$return"",""type"":""blob"",""direction"":""Out"",""blobPath"":""blobPath""}");
218+
Function0RawBindings.Add(@"{""name"":""context"",""type"":""mcpToolTrigger"",""direction"":""In"",""toolName"":""someString"",""description"":""someString""}");
219+
Function0RawBindings.Add(@"{""name"":""name"",""type"":""mcpToolProperty"",""direction"":""In"",""propertyName"":""someString"",""description"":""someString"",""isRequired"":false,""dataType"":""String""}");
220+
221+
var Function0 = new DefaultFunctionMetadata
222+
{
223+
Language = "dotnet-isolated",
224+
Name = "SaveSnippetFunction",
225+
EntryPoint = "MyCompany.Task.SaveSnippetFunction.SaveSnippet",
226+
RawBindings = Function0RawBindings,
227+
ScriptFile = "TestProject.dll"
228+
};
229+
metadataList.Add(Function0);
230+
231+
return global::System.Threading.Tasks.Task.FromResult(metadataList.ToImmutableArray());
232+
}
233+
}
234+
235+
/// <summary>
236+
/// Extension methods to enable registration of the custom <see cref="IFunctionMetadataProvider"/> implementation generated for the current worker.
237+
/// </summary>
238+
{{Constants.GeneratedCodeAttribute}}
239+
public static class WorkerHostBuilderFunctionMetadataProviderExtension
240+
{
241+
///<summary>
242+
/// Adds the GeneratedFunctionMetadataProvider to the service collection.
243+
/// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing.
244+
///</summary>
245+
public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder)
246+
{
247+
builder.ConfigureServices(s =>
248+
{
249+
s.AddSingleton<IFunctionMetadataProvider, GeneratedFunctionMetadataProvider>();
250+
});
251+
return builder;
252+
}
253+
}
254+
}
255+
""";
256+
257+
await TestHelpers.RunTestAsync<FunctionMetadataProviderGenerator>(
258+
_referencedExtensionAssemblies,
259+
inputCode,
260+
expectedGeneratedFileName,
261+
expectedOutput,
262+
languageVersion: languageVersion);
263+
}
264+
}
265+
}
266+
}

test/Sdk.Generator.Tests/FunctionMetadataProvider/RetryOptionsTests.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.Reflection;
@@ -27,7 +27,6 @@ public RetryOptionsTests()
2727
var diExtension = typeof(DefaultServiceProviderFactory).Assembly;
2828
var hostingAbExtension = typeof(IHost).Assembly;
2929
var diAbExtension = typeof(IServiceCollection).Assembly;
30-
var cosmosDBExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.CosmosDB.dll");
3130
var timerExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Timer.dll");
3231

3332

@@ -39,8 +38,7 @@ public RetryOptionsTests()
3938
hostingAbExtension,
4039
diExtension,
4140
diAbExtension,
42-
timerExtension,
43-
cosmosDBExtension
41+
timerExtension
4442
};
4543
}
4644

test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@
1616
</ItemGroup>
1717

1818
<ItemGroup>
19-
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
19+
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
2020
<PackageReference Include="coverlet.collector" Version="6.0.2">
2121
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2222
<PrivateAssets>all</PrivateAssets>
2323
</PackageReference>
24-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
24+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
25+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
2526
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="6.6.0" />
2627
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.22.0" />
2728
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Tables" Version="1.4.2" />
28-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.CosmosDB" Version="4.11.0" />
2929
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventHubs" Version="6.3.6" />
3030
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.1" />
31-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Kafka" Version="3.10.1" />
32-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.14.1" />
31+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Kafka" Version="4.1.3" />
32+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="2.0.1" />
3333
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.11.48" />
3434
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
3535
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />

0 commit comments

Comments
 (0)