sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.cs (100 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System; using System.Collections.Generic; 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 { /// <summary> /// Generates a class that implements IFunctionMetadataProvider and the method GetFunctionsMetadataAsync() which returns a list of IFunctionMetadata. /// This source generator indexes a Function App and explicitly creates a list of DefaultFunctionMetadata (which implements IFunctionMetadata) from the functions defined /// in the user's compilation. This allows the worker to index functions at build time, rather than waiting for the process to start. /// </summary> [Generator] public partial class FunctionMetadataProviderGenerator : ISourceGenerator { private const string NetFxTargetFrameworkIdentifierValue = ".NETFramework"; private const string ExecutableFileExtension = ".exe"; private const string DynamicLinkLibraryFileExtension = ".dll"; public void Execute(GeneratorExecutionContext context) { if (!context.IsRunningInAzureFunctionProject()) { return; } if (!ShouldExecuteGeneration(context)) { return; } if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver) { return; } var entryAssemblyFunctionSymbols = GetEntryAssemblyFunctions(receiver.CandidateMethods, context).ToList(); var dependentAssemblyFunctionSymbols = GetDependentAssemblyFunctions(context); if (entryAssemblyFunctionSymbols.Count == 0 && dependentAssemblyFunctionSymbols.Count == 0) { return; } var parser = new Parser(context); var entryAssemblyParsingContext = new FunctionsMetadataParsingContext { ScriptFileExtension = GetScriptFileExtensionForEntryPointAssemblyFunctions(context) }; var entryAssemblyFunctions = parser.GetFunctionMetadataInfo(entryAssemblyFunctionSymbols, entryAssemblyParsingContext); var dependentAssemblyFunctions = parser.GetFunctionMetadataInfo(dependentAssemblyFunctionSymbols); IReadOnlyList<GeneratorFunctionMetadata> functionMetadataInfo = entryAssemblyFunctions.Concat(dependentAssemblyFunctions).ToList(); // Proceed to generate the file if function metadata info was successfully returned if (functionMetadataInfo.Count > 0) { var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context); string result = Emitter.Emit(context, functionMetadataInfo, shouldIncludeAutoGeneratedAttributes); context.AddSource(Constants.FileNames.GeneratedFunctionMetadata, SourceText.From(result, Encoding.UTF8)); } } /// <summary> /// Register a factory that can create our custom syntax receiver /// </summary> /// <param name="context"></param> public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new FunctionMethodSyntaxReceiver()); } // For dependent assemblies, it will be always "dll". We only need to find out for entry point assembly. private static string GetScriptFileExtensionForEntryPointAssemblyFunctions(GeneratorExecutionContext context) { context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(Constants.BuildProperties.MSBuildTargetFrameworkIdentifier, out var value); return string.Equals(value, NetFxTargetFrameworkIdentifierValue, StringComparison.OrdinalIgnoreCase) ? ExecutableFileExtension : DynamicLinkLibraryFileExtension; } private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionContext context) { if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue( Constants.BuildProperties.AutoRegisterGeneratedMetadataProvider, out var autoRegisterSwitch)) { return false; } bool.TryParse(autoRegisterSwitch, out bool enableRegistration); return enableRegistration; } private static bool ShouldExecuteGeneration(GeneratorExecutionContext context) { if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue( Constants.BuildProperties.EnableMetadataSourceGen, out var sourceGenSwitch)) { return false; } bool.TryParse(sourceGenSwitch, out bool enableSourceGen); return enableSourceGen; } private IEnumerable<IMethodSymbol> GetEntryAssemblyFunctions(List<MethodDeclarationSyntax> candidateMethods, GeneratorExecutionContext context) { 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)!; yield return methodSymbol; } } } /// <summary> /// Collect methods with Function attributes on them from dependent/referenced assemblies. /// </summary> private List<IMethodSymbol> GetDependentAssemblyFunctions(GeneratorExecutionContext context) { var visitor = new ReferencedAssemblyMethodVisitor(context.Compilation); visitor.Visit(context.Compilation.SourceModule); return visitor.FunctionMethods; } } }