From 41c133aeb18479c6b2f28883341305eb772a9f3b Mon Sep 17 00:00:00 2001 From: Sarah Vu Date: Wed, 30 Aug 2023 15:15:50 -0700 Subject: [PATCH 1/5] initial wip --- .../FunctionMetadataProviderGenerator.cs | 47 +++++++++++++++++++ sdk/Sdk.Generators/FunctionsUtil.cs | 14 +++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs index f218d39b4..82d30de4e 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs @@ -2,8 +2,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators @@ -68,5 +71,49 @@ private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionConte return string.Equals(value, bool.TrueString, System.StringComparison.OrdinalIgnoreCase); } + + /// + /// Collect methods with Function attributes on them from dependent/referenced assemblies. + /// + private IList GetDependentAssemblyFunctions(GeneratorExecutionContext context) + { + IList? dependentAssemblyFuncs = new List(); + + foreach (var assembly in context.Compilation.SourceModule.ReferencedAssemblySymbols) + { + string? funcName = null; + + if (assembly.MetadataName == "LibraryWithFunctions") + { + var namespaceSymbols = assembly.GlobalNamespace.GetMembers(); + + foreach (var namespaceSymbol in namespaceSymbols) + { + var namespaceMembers = namespaceSymbol.GetMembers(); + + foreach (var m in namespaceMembers) + { + if (m is INamedTypeSymbol namedType) + { + var typeMembers = namedType.GetMembers(); + + foreach (var typeMember in typeMembers) + { + if (typeMember is IMethodSymbol method) + { + if (FunctionsUtil.IsFunctionSymbol(method, context.Compilation, out funcName)) + { + var syntax = method.DeclaringSyntaxReferences.FirstOrDefault(); + } + } + } + } + } + } + } + } + + return dependentAssemblyFuncs.Count > 0 ? dependentAssemblyFuncs : ImmutableList.Empty; + } } } diff --git a/sdk/Sdk.Generators/FunctionsUtil.cs b/sdk/Sdk.Generators/FunctionsUtil.cs index d8d2ee0fc..64258680e 100644 --- a/sdk/Sdk.Generators/FunctionsUtil.cs +++ b/sdk/Sdk.Generators/FunctionsUtil.cs @@ -28,7 +28,19 @@ internal static bool IsValidFunctionMethod( return false; } - foreach (var attr in methodSymbol.GetAttributes()) + if (IsFunctionSymbol(methodSymbol, compilation, out functionName)) + { + return true; + } + + return false; + } + + internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation, out string? functionName) + { + functionName = null; + + foreach (var attr in symbol.GetAttributes()) { if (attr.AttributeClass != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))) From 2564572138a8342b7b283f55fef7628cb8fbf6b1 Mon Sep 17 00:00:00 2001 From: Sarah Vu Date: Mon, 11 Sep 2023 14:04:00 -0700 Subject: [PATCH 2/5] refactor to use symbols only to walk dependencies --- DotNetWorker.sln | 7 + .../FunctionExecutorGenerator.Parser.cs | 3 +- ...dataProviderGenerator.CardinalityParser.cs | 13 +- ...unctionMetadataProviderGenerator.Parser.cs | 94 ++++++------ .../FunctionMetadataProviderGenerator.cs | 56 ++++--- sdk/Sdk.Generators/FunctionsUtil.cs | 40 ++++- test/DependentAssemblyWithFunctions/Class1.cs | 31 ++++ .../DependentAssemblyWithFunctions.csproj | 14 ++ .../DependentAssemblyTest.cs | 138 ++++++++++++++++++ .../DiagnosticResultTests.cs | 4 - .../EventHubsBindingsTests.cs | 1 - .../Sdk.Generator.Tests.csproj | 1 + 12 files changed, 306 insertions(+), 96 deletions(-) create mode 100644 test/DependentAssemblyWithFunctions/Class1.cs create mode 100644 test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj create mode 100644 test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs diff --git a/DotNetWorker.sln b/DotNetWorker.sln index f18ed5711..2fe0c13d0 100644 --- a/DotNetWorker.sln +++ b/DotNetWorker.sln @@ -132,6 +132,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Worker.Extensions.Tests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetIntegration", "samples\AspNetIntegration\AspNetIntegration.csproj", "{D2F67410-9933-42E8-B04A-E17634D83A30}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependentAssemblyWithFunctions", "test\DependentAssemblyWithFunctions\DependentAssemblyWithFunctions.csproj", "{AB6E1E7A-0D2C-4086-9487-566887C1E753}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -326,6 +328,10 @@ Global {D2F67410-9933-42E8-B04A-E17634D83A30}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2F67410-9933-42E8-B04A-E17634D83A30}.Release|Any CPU.Build.0 = Release|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB6E1E7A-0D2C-4086-9487-566887C1E753}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -383,6 +389,7 @@ Global {286F9EE3-00AE-4EFA-BFD8-A2E58BC809D2} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} {17BDCE12-6964-4B87-B2AC-68CE270A3E9A} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378} {D2F67410-9933-42E8-B04A-E17634D83A30} = {9D6603BD-7EA2-4D11-A69C-0D9E01317FD6} + {AB6E1E7A-0D2C-4086-9487-566887C1E753} = {B5821230-6E0A-4535-88A9-ED31B6F07596} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A} diff --git a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs index 6e7a88872..ae160a342 100644 --- a/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Parser.cs @@ -32,8 +32,7 @@ internal ICollection GetFunctions(List /// The parameter associated with a binding attribute that supports cardinality represented as an . - /// The representation of the paramter's type if it exists. /// The binding attribute that supports cardinality. /// The that best represents the parameter. /// Returns true if the parameter is compatible with the cardinality defined by the attribute, else returns false. - public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? parameterTypeSyntax, AttributeData attribute, out DataType dataType) + public bool IsCardinalityValid(IParameterSymbol parameterSymbol, AttributeData attribute, out DataType dataType) { dataType = DataType.Undefined; var cardinalityIsNamedArg = false; @@ -139,12 +138,7 @@ public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? par } else if (isGenericEnumerable) { - if (parameterTypeSyntax is null) - { - return false; - } - - dataType = ResolveIEnumerableOfT(parameterSymbol, parameterTypeSyntax, out bool hasError); + dataType = ResolveIEnumerableOfT(parameterSymbol, out bool hasError); if (hasError) { @@ -164,10 +158,9 @@ public bool IsCardinalityValid(IParameterSymbol parameterSymbol, TypeSyntax? par /// Find the underlying data type of an IEnumerableOfT (String, Binary, Undefined) /// ex. IEnumerable would return DataType.Binary /// - private DataType ResolveIEnumerableOfT(IParameterSymbol parameterSymbol, TypeSyntax parameterSyntax, out bool hasError) + private DataType ResolveIEnumerableOfT(IParameterSymbol parameterSymbol, out bool hasError) { var result = DataType.Undefined; - var currentSyntax = parameterSyntax; hasError = false; var currSymbol = parameterSymbol.Type; diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index 5ba6d02c7..85bdf701f 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -42,7 +42,7 @@ public Parser(GeneratorExecutionContext context) /// Takes in candidate methods from the user compilation and parses them to return function metadata info as GeneratorFunctionMetadata. /// /// List of candidate methods from the syntax receiver. - public IReadOnlyList GetFunctionMetadataInfo(List methods) + public IReadOnlyList GetFunctionMetadataInfo(List methods) { var result = ImmutableArray.CreateBuilder(); @@ -50,26 +50,25 @@ public IReadOnlyList GetFunctionMetadataInfo(List>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? retryOptions)) + if (!TryGetBindings(method, out IList>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? retryOptions)) { continue; } @@ -92,14 +91,14 @@ public IReadOnlyList GetFunctionMetadataInfo(List>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? validatedRetryOptions) + private bool TryGetBindings(IMethodSymbol method, out IList>? bindings, out bool hasHttpTrigger, out GeneratorRetryOptions? validatedRetryOptions) { hasHttpTrigger = false; validatedRetryOptions = null; - if (!TryGetMethodOutputBinding(method, model, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? methodOutputBindings) - || !TryGetParameterInputAndTriggerBindings(method, model, out bool supportsRetryOptions, out hasHttpTrigger, out IList>? parameterInputAndTriggerBindings) - || !TryGetReturnTypeBindings(method, model, hasHttpTrigger, hasOutputBinding, out IList>? returnTypeBindings)) + if (!TryGetMethodOutputBinding(method, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? methodOutputBindings) + || !TryGetParameterInputAndTriggerBindings(method, out bool supportsRetryOptions, out hasHttpTrigger, out IList>? parameterInputAndTriggerBindings) + || !TryGetReturnTypeBindings(method, hasHttpTrigger, hasOutputBinding, out IList>? returnTypeBindings)) { bindings = null; return false; @@ -121,7 +120,7 @@ private bool TryGetBindings(MethodDeclarationSyntax method, SemanticModel model, } else if (!supportsRetryOptions) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidRetryOptions, method.GetLocation())); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidRetryOptions, Location.None)); return false; } } @@ -132,12 +131,9 @@ private bool TryGetBindings(MethodDeclarationSyntax method, SemanticModel model, /// /// Checks for and returns any OutputBinding attributes associated with the method. /// - private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticModel model, out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) + private bool TryGetMethodOutputBinding(IMethodSymbol method,out bool hasOutputBinding, out GeneratorRetryOptions? retryOptions, out IList>? bindingsList) { - var bindingLocation = method.Identifier.GetLocation(); - - var methodSymbol = model.GetDeclaredSymbol(method); - var attributes = methodSymbol!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created + var attributes = method!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created AttributeData? outputBindingAttribute = null; hasOutputBinding = false; @@ -147,7 +143,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM { if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass?.BaseType, _knownFunctionMetadataTypes.RetryAttribute)) { - if (TryGetRetryOptionsFromAttribute(attribute, method.GetLocation(), out GeneratorRetryOptions? retryOptionsFromAttr)) + if (TryGetRetryOptionsFromAttribute(attribute, Location.None, out GeneratorRetryOptions? retryOptionsFromAttr)) { retryOptions = retryOptionsFromAttr; } @@ -158,7 +154,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM // There can only be one output binding associated with a function. If there is more than one, we return a diagnostic error here. if (hasOutputBinding) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, bindingLocation, new object[] { "Method", method.Identifier.ToString() })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, Location.None, new object[] { "Method", method.Name })); bindingsList = null; return false; } @@ -170,7 +166,7 @@ private bool TryGetMethodOutputBinding(MethodDeclarationSyntax method, SemanticM if (outputBindingAttribute != null) { - if (!TryCreateBindingDict(outputBindingAttribute, Constants.FunctionMetadataBindingProps.ReturnBindingName, bindingLocation, out IDictionary? bindingDict)) + if (!TryCreateBindingDict(outputBindingAttribute, Constants.FunctionMetadataBindingProps.ReturnBindingName, Location.None, out IDictionary? bindingDict)) { bindingsList = null; return false; @@ -244,29 +240,22 @@ private bool TryGetRetryOptionsFromAttribute(AttributeData attribute, Location l /// /// Checks for and returns input and trigger bindings found in the parameters of the Azure Function method. /// - private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax method, SemanticModel model, out bool supportsRetryOptions, out bool hasHttpTrigger, out IList>? bindingsList) + private bool TryGetParameterInputAndTriggerBindings(IMethodSymbol method, out bool supportsRetryOptions, out bool hasHttpTrigger, out IList>? bindingsList) { supportsRetryOptions = false; hasHttpTrigger = false; bindingsList = new List>(); - foreach (ParameterSyntax parameter in method.ParameterList.Parameters) + foreach (IParameterSymbol parameter in method.Parameters) { // If there's no attribute, we can assume that this parameter is not a binding - if (parameter.AttributeLists.Count == 0) + if (parameter.GetAttributes().Count() == 0) { continue; } - if (model.GetDeclaredSymbol(parameter) is not IParameterSymbol parameterSymbol) - { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, parameter.Identifier.GetLocation(), nameof(parameterSymbol))); - bindingsList = null; - return false; - } - // Check to see if any of the attributes associated with this parameter is a BindingAttribute - foreach (var attribute in parameterSymbol.GetAttributes()) + foreach (var attribute in parameter.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass?.BaseType?.BaseType, _knownFunctionMetadataTypes.BindingAttribute)) { @@ -276,12 +265,12 @@ private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax meth hasHttpTrigger = true; } - DataType dataType = _dataTypeParser.GetDataType(parameterSymbol.Type); + DataType dataType = _dataTypeParser.GetDataType(parameter.Type); bool cardinalityValidated = false; bool supportsDeferredBinding = false; - if (SupportsDeferredBinding(attribute, parameterSymbol.Type.ToString())) + if (SupportsDeferredBinding(attribute, parameter.Type.ToString())) { supportsDeferredBinding = true; } @@ -290,9 +279,9 @@ private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax meth { DataType updatedDataType = DataType.Undefined; - if (!_cardinalityParser.IsCardinalityValid(parameterSymbol, parameter.Type, attribute, out updatedDataType)) + if (!_cardinalityParser.IsCardinalityValid(parameter, attribute, out updatedDataType)) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidCardinality, parameter.Identifier.GetLocation(), parameterSymbol.Name)); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.InvalidCardinality, Location.None, parameter.Name)); bindingsList = null; return false; } @@ -304,9 +293,9 @@ private bool TryGetParameterInputAndTriggerBindings(MethodDeclarationSyntax meth cardinalityValidated = true; } - string bindingName = parameter.Identifier.ValueText; + string bindingName = parameter.Name; - if (!TryCreateBindingDict(attribute, bindingName, parameter.Identifier.GetLocation(), out IDictionary? bindingDict, supportsDeferredBinding)) + if (!TryCreateBindingDict(attribute, bindingName, Location.None, out IDictionary? bindingDict, supportsDeferredBinding)) { bindingsList = null; return false; @@ -418,15 +407,14 @@ private bool DoesConverterSupportTargetType(List converterAdverti /// /// Checks for and returns any bindings found in the Return Type of the method /// - private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticModel model, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) + private bool TryGetReturnTypeBindings(IMethodSymbol method, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) { - TypeSyntax returnTypeSyntax = method.ReturnType; - ITypeSymbol? returnTypeSymbol = model.GetSymbolInfo(returnTypeSyntax).Symbol as ITypeSymbol; + ITypeSymbol? returnTypeSymbol = method.ReturnType; bindingsList = new List>(); if (returnTypeSymbol is null) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, returnTypeSyntax.GetLocation(), nameof(returnTypeSymbol))); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, Location.None, nameof(returnTypeSymbol))); bindingsList = null; return false; } @@ -437,13 +425,17 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo // If there is a Task return type, inspect T, the inner type. if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskOfTType)) { - GenericNameSyntax genericSyntax = (GenericNameSyntax)returnTypeSyntax; - var innerTypeSyntax = genericSyntax.TypeArgumentList.Arguments.First(); // Generic task should only have one type argument - returnTypeSymbol = model.GetSymbolInfo(innerTypeSyntax).Symbol as ITypeSymbol; + if (returnTypeSymbol is INamedTypeSymbol namedTypeSymbol) + { + if (namedTypeSymbol.IsGenericType) + { + returnTypeSymbol = namedTypeSymbol.TypeArguments.FirstOrDefault();// Generic task should only have one type argument + } + } if (returnTypeSymbol is null) // need this check here b/c return type symbol takes on a new value from the inner argument type above { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, genericSyntax.Identifier.GetLocation(), nameof(returnTypeSymbol))); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.SymbolNotFound, Location.None, nameof(returnTypeSymbol))); bindingsList = null; return false; } @@ -455,7 +447,7 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo } else { - if (!TryGetReturnTypePropertyBindings(returnTypeSymbol, hasHttpTrigger, hasOutputBinding, returnTypeSyntax.GetLocation(), out bindingsList)) + if (!TryGetReturnTypePropertyBindings(returnTypeSymbol, hasHttpTrigger, hasOutputBinding, out bindingsList)) { bindingsList = null; return false; @@ -466,7 +458,7 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo return true; } - private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool hasHttpTrigger, bool hasOutputBinding, Location returnTypeLocation, out IList>? bindingsList) + private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool hasHttpTrigger, bool hasOutputBinding, out IList>? bindingsList) { var members = returnTypeSymbol.GetMembers(); var foundHttpOutput = false; @@ -486,7 +478,7 @@ private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool { if (foundHttpOutput) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleHttpResponseTypes, returnTypeLocation, new object[] { returnTypeSymbol.Name })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleHttpResponseTypes, Location.None, new object[] { returnTypeSymbol.Name })); bindingsList = null; return false; } @@ -505,7 +497,7 @@ private bool TryGetReturnTypePropertyBindings(ITypeSymbol returnTypeSymbol, bool // validate that there's only one binding attribute per property if (foundPropertyOutputAttr) { - _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, prop.Locations.FirstOrDefault(), new object[] { "Property", prop.Name.ToString() })); + _context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MultipleBindingsGroupedTogether, Location.None, new object[] { "Property", prop.Name.ToString() })); bindingsList = null; return false; } diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs index 82d30de4e..d35b37a5f 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs @@ -38,7 +38,10 @@ public void Execute(GeneratorExecutionContext context) // attempt to parse user compilation var p = new Parser(context); - IReadOnlyList functionMetadataInfo = p.GetFunctionMetadataInfo(receiver.CandidateMethods); + var entryAssemblyFuncs = GetEntryAssemblyFunctions(receiver.CandidateMethods, context); + var dependentFuncs = GetDependentAssemblyFunctions(context); + + IReadOnlyList functionMetadataInfo = p.GetFunctionMetadataInfo(entryAssemblyFuncs.Concat(dependentFuncs).ToList()); // Proceed to generate the file if function metadata info was successfully returned if (functionMetadataInfo.Count > 0) @@ -72,39 +75,52 @@ private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionConte return string.Equals(value, bool.TrueString, System.StringComparison.OrdinalIgnoreCase); } + private IList GetEntryAssemblyFunctions(List candidateMethods, GeneratorExecutionContext context) + { + IList? entryAssemblyFuncs = new List(); + + foreach (MethodDeclarationSyntax method in candidateMethods) + { + var model = context.Compilation.GetSemanticModel(method.SyntaxTree); + + if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method)) + { + IMethodSymbol? methodSymbol = (IMethodSymbol) model.GetDeclaredSymbol(method)!; + entryAssemblyFuncs.Add(methodSymbol); + } + } + + return entryAssemblyFuncs.Count > 0 ? entryAssemblyFuncs : ImmutableList.Empty; + } + /// /// Collect methods with Function attributes on them from dependent/referenced assemblies. /// - private IList GetDependentAssemblyFunctions(GeneratorExecutionContext context) + private IList GetDependentAssemblyFunctions(GeneratorExecutionContext context) { - IList? dependentAssemblyFuncs = new List(); + IList? dependentAssemblyFuncs = new List(); foreach (var assembly in context.Compilation.SourceModule.ReferencedAssemblySymbols) { - string? funcName = null; + var namespaceSymbols = assembly.GlobalNamespace.GetMembers(); - if (assembly.MetadataName == "LibraryWithFunctions") + foreach (var namespaceSymbol in namespaceSymbols) { - var namespaceSymbols = assembly.GlobalNamespace.GetMembers(); + var namespaceMembers = namespaceSymbol.GetMembers(); - foreach (var namespaceSymbol in namespaceSymbols) + foreach (var m in namespaceMembers) { - var namespaceMembers = namespaceSymbol.GetMembers(); - - foreach (var m in namespaceMembers) + if (m is INamedTypeSymbol namedType) { - if (m is INamedTypeSymbol namedType) - { - var typeMembers = namedType.GetMembers(); + var typeMembers = namedType.GetMembers(); - foreach (var typeMember in typeMembers) + foreach (var typeMember in typeMembers) + { + if (typeMember is IMethodSymbol method) { - if (typeMember is IMethodSymbol method) + if (FunctionsUtil.IsFunctionSymbol(method, context.Compilation)) { - if (FunctionsUtil.IsFunctionSymbol(method, context.Compilation, out funcName)) - { - var syntax = method.DeclaringSyntaxReferences.FirstOrDefault(); - } + dependentAssemblyFuncs.Add(method); } } } @@ -113,7 +129,7 @@ private IList GetDependentAssemblyFunctions(GeneratorEx } } - return dependentAssemblyFuncs.Count > 0 ? dependentAssemblyFuncs : ImmutableList.Empty; + return dependentAssemblyFuncs.Count > 0 ? dependentAssemblyFuncs : ImmutableList.Empty; } } } diff --git a/sdk/Sdk.Generators/FunctionsUtil.cs b/sdk/Sdk.Generators/FunctionsUtil.cs index 64258680e..e13225ccb 100644 --- a/sdk/Sdk.Generators/FunctionsUtil.cs +++ b/sdk/Sdk.Generators/FunctionsUtil.cs @@ -4,6 +4,9 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Data; +using System; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators { @@ -16,10 +19,8 @@ internal static bool IsValidFunctionMethod( GeneratorExecutionContext context, Compilation compilation, SemanticModel model, - MethodDeclarationSyntax method, - out string? functionName) + MethodDeclarationSyntax method) { - functionName = null; var methodSymbol = model.GetDeclaredSymbol(method); if (methodSymbol is null) @@ -28,7 +29,7 @@ internal static bool IsValidFunctionMethod( return false; } - if (IsFunctionSymbol(methodSymbol, compilation, out functionName)) + if (IsFunctionSymbol(methodSymbol, compilation)) { return true; } @@ -36,16 +37,13 @@ internal static bool IsValidFunctionMethod( return false; } - internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation, out string? functionName) + internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation) { - functionName = null; - foreach (var attr in symbol.GetAttributes()) { if (attr.AttributeClass != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))) { - functionName = (string)attr.ConstructorArguments.First().Value!; // If this is a function attribute this won't be null return true; } } @@ -53,6 +51,21 @@ internal static bool IsFunctionSymbol(ISymbol symbol, Compilation compilation, o return false; } + internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, out string? functionName) + { + functionName = null; + + var functionAttribute = symbol.GetAttributes().Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))); + + if (functionAttribute.Count() > 0) + { + functionName = (string)functionAttribute.FirstOrDefault().ConstructorArguments.First().Value!; // If this is a function attribute this won't be null + return true; + } + + return false; + } + /// /// Gets the fully qualified name of the method. /// Ex: "MyNamespaceName.MyClassName.MyMethod" @@ -65,5 +78,16 @@ internal static string GetFullyQualifiedMethodName(MethodDeclarationSyntax metho return $"{fullyQualifiedClassName}.{method.Identifier.ValueText}"; } + + internal static string GetFullyQualifiedMethodName(IMethodSymbol method) + { + var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(); + return $"{fullyQualifiedClassName}.{method.Name}"; + } + + internal static string GetFullyQualifiedMetadataName(IMethodSymbol method) + { + throw new NotImplementedException(); + } } } diff --git a/test/DependentAssemblyWithFunctions/Class1.cs b/test/DependentAssemblyWithFunctions/Class1.cs new file mode 100644 index 000000000..421700276 --- /dev/null +++ b/test/DependentAssemblyWithFunctions/Class1.cs @@ -0,0 +1,31 @@ + +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace DependentAssemblyWithFunctions +{ + public class DependencyFunc + { + private readonly ILogger _logger; + + public DependencyFunc(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + [Function("DependencyFunc")] + public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) + { + _logger.LogInformation("C# HTTP trigger function processed a request."); + + var response = req.CreateResponse(HttpStatusCode.OK); + response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); + + response.WriteString("Welcome to Azure Functions!"); + + return response; + } + } +} diff --git a/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj b/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj new file mode 100644 index 000000000..936bc230d --- /dev/null +++ b/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs new file mode 100644 index 000000000..db1487ae7 --- /dev/null +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs @@ -0,0 +1,138 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Azure.Functions.Worker.Sdk.Generators; +using Xunit; + +namespace Microsoft.Azure.Functions.SdkGeneratorTests +{ + public partial class FunctionMetadataProviderGeneratorTests + { + public class DependentAssemblyTest + { + private readonly Assembly[] _referencedExtensionAssemblies; + + public DependentAssemblyTest() + { + // load all extensions used in tests (match extensions tested on E2E app? Or include ALL extensions?) + var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll"); + var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll"); + var hostingExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.dll"); + var diExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.dll"); + var hostingAbExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.Abstractions.dll"); + var diAbExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.Abstractions.dll"); + var dependentAssembly = Assembly.LoadFrom("DependentAssemblyWithFunctions.dll"); + + _referencedExtensionAssemblies = new[] + { + abstractionsExtension, + httpExtension, + hostingExtension, + hostingAbExtension, + diExtension, + diAbExtension, + dependentAssembly + }; + } + + [Fact] + public async Task SimpleFunctionInDependentAssemblyTest() + { + string inputCode = """ + using System; + using System.Collections.Generic; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + + namespace FunctionApp + { + public static class HttpTriggerSimple + { + [Function(nameof(HttpTriggerSimple))] + public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionContext executionContext) + { + throw new NotImplementedException(); + } + } + } + """; + + string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; + string expectedOutput = """ + // + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Text.Json; + using System.Threading.Tasks; + using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + namespace Microsoft.Azure.Functions.Worker + { + public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider + { + public Task> GetFunctionMetadataAsync(string directory) + { + var metadataList = new List(); + var Function0RawBindings = new List(); + Function0RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function0 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "HttpTriggerSimple", + EntryPoint = "FunctionApp.HttpTriggerSimple.Run", + RawBindings = Function0RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function0); + var Function1RawBindings = new List(); + Function1RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function1RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function1 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "DependencyFunc", + EntryPoint = "DependentAssemblyWithFunctions.DependencyFunc.Run", + RawBindings = Function1RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function1); + + return Task.FromResult(metadataList.ToImmutableArray()); + } + } + + public static class WorkerHostBuilderFunctionMetadataProviderExtension + { + /// + /// Adds the GeneratedFunctionMetadataProvider to the service collection. + /// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing. + /// + public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder) + { + builder.ConfigureServices(s => + { + s.AddSingleton(); + }); + return builder; + } + } + } + """; + + await TestHelpers.RunTestAsync( + _referencedExtensionAssemblies, + inputCode, + expectedGeneratedFileName, + expectedOutput); + } + } + } +} diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs index 8478337b2..d52f5cd29 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DiagnosticResultTests.cs @@ -76,7 +76,6 @@ public string QueueToBlob( var expectedDiagnosticResults = new List { new DiagnosticResult(DiagnosticDescriptors.MultipleBindingsGroupedTogether) - .WithSpan(17, 39, 17, 50) // these arguments are the values we pass as the message format parameters when creating the DiagnosticDescriptor instance. .WithArguments("Method", "QueueToBlob") }; @@ -131,7 +130,6 @@ public class MyOutputType var expectedDiagnosticResults = new List { new DiagnosticResult(DiagnosticDescriptors.MultipleBindingsGroupedTogether) - .WithSpan(28, 39, 28, 43) // these arguments are the values we pass as the message format parameters when creating the DiagnosticDescriptor instance. .WithArguments("Property", "Name") }; @@ -181,7 +179,6 @@ public class MultiReturnHttp var expectedDiagnosticResults = new List { new DiagnosticResult(DiagnosticDescriptors.MultipleHttpResponseTypes) - .WithSpan(11, 32, 11, 47) // these arguments are the values we pass as the message format parameters when creating the DiagnosticDescriptor instance. .WithArguments("MultiReturnHttp") }; @@ -221,7 +218,6 @@ public string Run([HttpTrigger(""get"")] string req) var expectedDiagnosticResults = new List { new DiagnosticResult(DiagnosticDescriptors.InvalidRetryOptions) - .WithSpan(10, 25, 15, 26) }; await TestHelpers.RunTestAsync( diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs index 63c5791d9..b09544bec 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/EventHubsBindingsTests.cs @@ -795,7 +795,6 @@ public static void InvalidEventHubsTrigger([EventHubTrigger(""test"", Connection var expectedDiagnosticResults = new List { new DiagnosticResult(DiagnosticDescriptors.InvalidCardinality) - .WithSpan(15, 146, 15, 151) // these arguments are the values we pass as the message format parameters when creating the DiagnosticDescriptor instance. .WithArguments("input") }; diff --git a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj index a5cbcdfee..838a3f7ea 100644 --- a/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj +++ b/test/Sdk.Generator.Tests/Sdk.Generator.Tests.csproj @@ -45,6 +45,7 @@ + From 8412f11c83ca04bf97352db43220407130856e9b Mon Sep 17 00:00:00 2001 From: Sarah Vu Date: Mon, 11 Sep 2023 14:09:52 -0700 Subject: [PATCH 3/5] add release notes --- sdk/release_notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 5a99a0a84..97a5bf0eb 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -14,4 +14,5 @@ ### Microsoft.Azure.Functions.Worker.Sdk.Generators -- Parse named arguments by type (#1877) \ No newline at end of file +- Parse named arguments by type (#1877) +- Refactor source gen to walk dependent assemblies (#1896) \ No newline at end of file From 05ba77b88c7585e65ebb7be1eb148dc807d74185 Mon Sep 17 00:00:00 2001 From: Sarah Vu Date: Wed, 13 Sep 2023 14:22:16 -0700 Subject: [PATCH 4/5] resolve comments --- .../FunctionMetadataProviderGenerator.Parser.cs | 6 +++--- .../FunctionMetadataProviderGenerator.cs | 14 ++++---------- sdk/Sdk.Generators/FunctionsUtil.cs | 15 ++++++--------- .../DependentAssemblyTest.cs | 2 +- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index 85bdf701f..e10357f28 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -46,9 +46,6 @@ public IReadOnlyList GetFunctionMetadataInfo(List(); - var assemblyName = Compilation.Assembly.Name; - var scriptFile = Path.Combine(assemblyName + ".dll"); - // Loop through the candidate methods (methods with any attribute associated with them) foreach (IMethodSymbol method in methods) { @@ -60,6 +57,9 @@ public IReadOnlyList GetFunctionMetadataInfo(List GetEntryAssemblyFunctions(List candidateMethods, GeneratorExecutionContext context) + private IEnumerable GetEntryAssemblyFunctions(List candidateMethods, GeneratorExecutionContext context) { IList? entryAssemblyFuncs = new List(); @@ -86,20 +86,16 @@ private IList GetEntryAssemblyFunctions(List 0 ? entryAssemblyFuncs : ImmutableList.Empty; } /// /// Collect methods with Function attributes on them from dependent/referenced assemblies. /// - private IList GetDependentAssemblyFunctions(GeneratorExecutionContext context) + private IEnumerable GetDependentAssemblyFunctions(GeneratorExecutionContext context) { - IList? dependentAssemblyFuncs = new List(); - foreach (var assembly in context.Compilation.SourceModule.ReferencedAssemblySymbols) { var namespaceSymbols = assembly.GlobalNamespace.GetMembers(); @@ -120,7 +116,7 @@ private IList GetDependentAssemblyFunctions(GeneratorExecutionCon { if (FunctionsUtil.IsFunctionSymbol(method, context.Compilation)) { - dependentAssemblyFuncs.Add(method); + yield return method; } } } @@ -128,8 +124,6 @@ private IList GetDependentAssemblyFunctions(GeneratorExecutionCon } } } - - return dependentAssemblyFuncs.Count > 0 ? dependentAssemblyFuncs : ImmutableList.Empty; } } } diff --git a/sdk/Sdk.Generators/FunctionsUtil.cs b/sdk/Sdk.Generators/FunctionsUtil.cs index e13225ccb..98764ac71 100644 --- a/sdk/Sdk.Generators/FunctionsUtil.cs +++ b/sdk/Sdk.Generators/FunctionsUtil.cs @@ -55,11 +55,13 @@ internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, { functionName = null; - var functionAttribute = symbol.GetAttributes().Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))); - - if (functionAttribute.Count() > 0) + var functionAttribute = symbol.GetAttributes() + .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))) + .FirstOrDefault(); + + if (functionAttribute is not null) { - functionName = (string)functionAttribute.FirstOrDefault().ConstructorArguments.First().Value!; // If this is a function attribute this won't be null + functionName = (string) functionAttribute.ConstructorArguments.First().Value!; return true; } @@ -84,10 +86,5 @@ internal static string GetFullyQualifiedMethodName(IMethodSymbol method) var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(); return $"{fullyQualifiedClassName}.{method.Name}"; } - - internal static string GetFullyQualifiedMetadataName(IMethodSymbol method) - { - throw new NotImplementedException(); - } } } diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs index db1487ae7..641265fc0 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs @@ -101,7 +101,7 @@ public Task> GetFunctionMetadataAsync(string d Name = "DependencyFunc", EntryPoint = "DependentAssemblyWithFunctions.DependencyFunc.Run", RawBindings = Function1RawBindings, - ScriptFile = "TestProject.dll" + ScriptFile = "DependentAssemblyWithFunctions.dll" }; metadataList.Add(Function1); From bba544a4fd8c51caa698b9a5448f28c98d3316ae Mon Sep 17 00:00:00 2001 From: Sarah Vu Date: Fri, 15 Sep 2023 16:46:29 -0700 Subject: [PATCH 5/5] resolve comments --- ...unctionMetadataProviderGenerator.Parser.cs | 9 ++---- sdk/Sdk.Generators/FunctionsUtil.cs | 11 +------ test/DependentAssemblyWithFunctions/Class1.cs | 31 ------------------- .../DependencyFunction.cs | 17 ++++++++++ .../DependentAssemblyWithFunctions.csproj | 2 +- .../InternalFunction.cs | 15 +++++++++ .../StaticFunction.cs | 15 +++++++++ .../DependentAssemblyTest.cs | 30 ++++++++++++++++-- 8 files changed, 79 insertions(+), 51 deletions(-) delete mode 100644 test/DependentAssemblyWithFunctions/Class1.cs create mode 100644 test/DependentAssemblyWithFunctions/DependencyFunction.cs create mode 100644 test/DependentAssemblyWithFunctions/InternalFunction.cs create mode 100644 test/DependentAssemblyWithFunctions/StaticFunction.cs diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index bc7cad3f8..5d4ce348a 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -249,7 +249,7 @@ private bool TryGetParameterInputAndTriggerBindings(IMethodSymbol method, out bo foreach (IParameterSymbol parameter in method.Parameters) { // If there's no attribute, we can assume that this parameter is not a binding - if (parameter.GetAttributes().Count() == 0) + if (!parameter.GetAttributes().Any()) { continue; } @@ -268,12 +268,7 @@ private bool TryGetParameterInputAndTriggerBindings(IMethodSymbol method, out bo DataType dataType = _dataTypeParser.GetDataType(parameter.Type); bool cardinalityValidated = false; - bool supportsDeferredBinding = false; - - if (SupportsDeferredBinding(attribute, parameter.Type.ToString())) - { - supportsDeferredBinding = true; - } + bool supportsDeferredBinding = SupportsDeferredBinding(attribute, parameter.Type.ToString()); if (_cardinalityParser.IsCardinalitySupported(attribute)) { diff --git a/sdk/Sdk.Generators/FunctionsUtil.cs b/sdk/Sdk.Generators/FunctionsUtil.cs index 98764ac71..429400f27 100644 --- a/sdk/Sdk.Generators/FunctionsUtil.cs +++ b/sdk/Sdk.Generators/FunctionsUtil.cs @@ -56,8 +56,7 @@ internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, functionName = null; var functionAttribute = symbol.GetAttributes() - .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))) - .FirstOrDefault(); + .FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compilation.GetTypeByMetadataName(Constants.Types.FunctionName))); if (functionAttribute is not null) { @@ -73,14 +72,6 @@ internal static bool TryGetFunctionName(ISymbol symbol, Compilation compilation, /// Ex: "MyNamespaceName.MyClassName.MyMethod" /// for a method called "MyMethod" inside the "MyClassName" type which is inside the "MyNamespaceName" namespace. /// - internal static string GetFullyQualifiedMethodName(MethodDeclarationSyntax method, SemanticModel semanticModel) - { - var methodSymbol = semanticModel.GetDeclaredSymbol(method)!; - var fullyQualifiedClassName = methodSymbol.ContainingSymbol.ToDisplayString(); - - return $"{fullyQualifiedClassName}.{method.Identifier.ValueText}"; - } - internal static string GetFullyQualifiedMethodName(IMethodSymbol method) { var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(); diff --git a/test/DependentAssemblyWithFunctions/Class1.cs b/test/DependentAssemblyWithFunctions/Class1.cs deleted file mode 100644 index 421700276..000000000 --- a/test/DependentAssemblyWithFunctions/Class1.cs +++ /dev/null @@ -1,31 +0,0 @@ - -using System.Net; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; - -namespace DependentAssemblyWithFunctions -{ - public class DependencyFunc - { - private readonly ILogger _logger; - - public DependencyFunc(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - [Function("DependencyFunc")] - public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) - { - _logger.LogInformation("C# HTTP trigger function processed a request."); - - var response = req.CreateResponse(HttpStatusCode.OK); - response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); - - response.WriteString("Welcome to Azure Functions!"); - - return response; - } - } -} diff --git a/test/DependentAssemblyWithFunctions/DependencyFunction.cs b/test/DependentAssemblyWithFunctions/DependencyFunction.cs new file mode 100644 index 000000000..605b45343 --- /dev/null +++ b/test/DependentAssemblyWithFunctions/DependencyFunction.cs @@ -0,0 +1,17 @@ + +using System.Net; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace DependentAssemblyWithFunctions +{ + public class DependencyFunction + { + [Function("DependencyFunc")] + public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj b/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj index 936bc230d..34139f102 100644 --- a/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj +++ b/test/DependentAssemblyWithFunctions/DependentAssemblyWithFunctions.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/DependentAssemblyWithFunctions/InternalFunction.cs b/test/DependentAssemblyWithFunctions/InternalFunction.cs new file mode 100644 index 000000000..6a34b7388 --- /dev/null +++ b/test/DependentAssemblyWithFunctions/InternalFunction.cs @@ -0,0 +1,15 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace DependentAssemblyWithFunctions +{ + internal class InternalFunction + { + [Function(nameof(InternalFunction))] + public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, + FunctionContext executionContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/DependentAssemblyWithFunctions/StaticFunction.cs b/test/DependentAssemblyWithFunctions/StaticFunction.cs new file mode 100644 index 000000000..79c56a3e1 --- /dev/null +++ b/test/DependentAssemblyWithFunctions/StaticFunction.cs @@ -0,0 +1,15 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace DependentAssemblyWithFunctions +{ + public static class StaticFunction + { + [Function(nameof(StaticFunction))] + public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, + FunctionContext executionContext) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs index 641265fc0..621a7232d 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/DependentAssemblyTest.cs @@ -38,7 +38,7 @@ public DependentAssemblyTest() } [Fact] - public async Task SimpleFunctionInDependentAssemblyTest() + public async Task FunctionInDependentAssemblyTest() { string inputCode = """ using System; @@ -99,11 +99,37 @@ public Task> GetFunctionMetadataAsync(string d { Language = "dotnet-isolated", Name = "DependencyFunc", - EntryPoint = "DependentAssemblyWithFunctions.DependencyFunc.Run", + EntryPoint = "DependentAssemblyWithFunctions.DependencyFunction.Run", RawBindings = Function1RawBindings, ScriptFile = "DependentAssemblyWithFunctions.dll" }; metadataList.Add(Function1); + var Function2RawBindings = new List(); + Function2RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function2RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function2 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "InternalFunction", + EntryPoint = "DependentAssemblyWithFunctions.InternalFunction.Run", + RawBindings = Function2RawBindings, + ScriptFile = "DependentAssemblyWithFunctions.dll" + }; + metadataList.Add(Function2); + var Function3RawBindings = new List(); + Function3RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function3RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function3 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "StaticFunction", + EntryPoint = "DependentAssemblyWithFunctions.StaticFunction.Run", + RawBindings = Function3RawBindings, + ScriptFile = "DependentAssemblyWithFunctions.dll" + }; + metadataList.Add(Function3); return Task.FromResult(metadataList.ToImmutableArray()); }