diff --git a/MinimalLambda.sln.DotSettings b/MinimalLambda.sln.DotSettings index 7497ea41..7fa61dc4 100644 --- a/MinimalLambda.sln.DotSettings +++ b/MinimalLambda.sln.DotSettings @@ -1,6 +1,6 @@  DO_NOT_SHOW - <?xml version="1.0" encoding="utf-16"?><Profile name="Full Custom Cleanup"><CppReformatCode>True</CppReformatCode><FSharpReformatCode>True</FSharpReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSharpReformatComments>True</CSharpReformatComments><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortDefinitions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSReorderTypeMembers>True</CSReorderTypeMembers><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppRedundantDereferences>True</CppRedundantDereferences><CppDeleteRedundantAccessSpecifier>True</CppDeleteRedundantAccessSpecifier><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><VBMakeFieldReadonly>True</VBMakeFieldReadonly><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + <?xml version="1.0" encoding="utf-16"?><Profile name="Full Custom Cleanup"><CppReformatCode>True</CppReformatCode><FSharpReformatCode>True</FSharpReformatCode><ShaderLabReformatCode>True</ShaderLabReformatCode><XMLReformatCode>True</XMLReformatCode><VBReformatCode>True</VBReformatCode><CSReformatCode>True</CSReformatCode><CSharpReformatComments>True</CSharpReformatComments><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" ArrangeNullCheckingPattern="True" /><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortDefinitions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppRedundantDereferences>True</CppRedundantDereferences><CppDeleteRedundantAccessSpecifier>True</CppDeleteRedundantAccessSpecifier><CppRemoveCastDescriptor>True</CppRemoveCastDescriptor><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppMakeVariableConstexpr>True</CppMakeVariableConstexpr><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppTypeTraitAliasDescriptor>True</CppTypeTraitAliasDescriptor><CppRemoveRedundantConditionalExpressionDescriptor>True</CppRemoveRedundantConditionalExpressionDescriptor><CppSimplifyConditionalExpressionDescriptor>True</CppSimplifyConditionalExpressionDescriptor><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppReplaceTieWithStructuredBindingDescriptor>True</CppReplaceTieWithStructuredBindingDescriptor><CppUseAssociativeContainsDescriptor>True</CppUseAssociativeContainsDescriptor><CppUseEraseAlgorithmDescriptor>True</CppUseEraseAlgorithmDescriptor><CppJoinDeclarationAndAssignmentDescriptor>True</CppJoinDeclarationAndAssignmentDescriptor><CppMakeClassFinal>True</CppMakeClassFinal><CppMakeLocalVarConstDescriptor>True</CppMakeLocalVarConstDescriptor><CppMakeMethodConst>True</CppMakeMethodConst><CppMakeMethodStatic>True</CppMakeMethodStatic><CppMakePtrOrRefParameterConst>True</CppMakePtrOrRefParameterConst><CppMakeParameterConst>True</CppMakeParameterConst><CppPassValueParameterByConstReference>True</CppPassValueParameterByConstReference><CppRemoveElaboratedTypeSpecifierDescriptor>True</CppRemoveElaboratedTypeSpecifierDescriptor><CppRemoveRedundantLambdaParameterListDescriptor>True</CppRemoveRedundantLambdaParameterListDescriptor><CppRemoveRedundantMemberInitializerDescriptor>True</CppRemoveRedundantMemberInitializerDescriptor><CppRemoveRedundantParentheses>True</CppRemoveRedundantParentheses><CppRemoveTemplateArgumentsDescriptor>True</CppRemoveTemplateArgumentsDescriptor><CppRemoveUnreachableCode>True</CppRemoveUnreachableCode><CppRemoveUnusedIncludes>True</CppRemoveUnusedIncludes><CppRemoveUnusedLambdaCaptures>True</CppRemoveUnusedLambdaCaptures><CppReplaceIfWithIfConsteval>True</CppReplaceIfWithIfConsteval><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><VBMakeFieldReadonly>True</VBMakeFieldReadonly><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Full Custom Cleanup" /&gt; &lt;inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt; &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WARNING" enabled_by_default="true" /&gt; @@ -9,7 +9,7 @@ &lt;inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true" /&gt; &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; &lt;Language id="CSS"&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="EditorConfig"&gt; @@ -19,9 +19,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="HTML"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; &lt;/Language&gt; &lt;Language id="HTTP Request"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -39,9 +39,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="JavaScript"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; &lt;/Language&gt; &lt;Language id="Markdown"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; @@ -71,9 +71,12 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="XML"&gt; - &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Rearrange&gt;false&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="liquid"&gt; + &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="yaml"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs index 5cb8ae21..99dcc35e 100644 --- a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticGenerator.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Models; @@ -12,90 +11,30 @@ internal static List GenerateDiagnostics(CompilationInfo compilation { var diagnostics = new List(); - var delegateInfos = compilationInfo.MapHandlerInvocationInfos; - - // Validate parameters - foreach (var invocationInfo in delegateInfos) - // check for multiple parameters that use the `[FromEvent]` attribute - if ( - invocationInfo.DelegateInfo.Parameters.Count(p => p.Source == ParameterSource.Event) - > 1 - ) - diagnostics.AddRange( - invocationInfo - .DelegateInfo.Parameters.Where(p => p.Source == ParameterSource.Event) - .Select(p => - Diagnostic.Create( - Diagnostics.MultipleParametersUseAttribute, - p.LocationInfo?.ToLocation(), - AttributeConstants.FromEventAttribute - ) - ) - ); - - // check for invalid keyed service usage - MapHandler diagnostics.AddRange( - compilationInfo.MapHandlerInvocationInfos.GenerateKeyedServiceKeyDiagnostics() + compilationInfo + .MapHandlerInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) ); - // check for invalid keyed service usage - OnShutdown diagnostics.AddRange( - compilationInfo.OnShutdownInvocationInfos.GenerateKeyedServiceKeyDiagnostics() + compilationInfo + .OnInitInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) ); - foreach (var useMiddlewareTInfo in compilationInfo.UseMiddlewareTInfos) - { - // ensure middleware class is concrete - if (useMiddlewareTInfo.ClassInfo.TypeKind is "interface" or "abstract class") - { - diagnostics.Add( - Diagnostic.Create( - Diagnostics.MustBeConcreteType, - useMiddlewareTInfo.GenericTypeArgumentLocation?.ToLocation(), - useMiddlewareTInfo.ClassInfo.ShortName - ) - ); - } + diagnostics.AddRange( + compilationInfo + .OnShutdownInvocationInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); - // validate that middleware class constructors only use `[MiddlewareConstructor]` once - diagnostics.AddRange( - useMiddlewareTInfo - .ClassInfo.ConstructorInfos.Where(c => - c.AttributeInfos.Any(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - ) - .Skip(1) - .Select(c => - Diagnostic.Create( - Diagnostics.MultipleConstructorsWithAttribute, - c.AttributeInfos.First(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - .LocationInfo?.ToLocation(), - AttributeConstants.MiddlewareConstructor - ) - ) - ); - } + diagnostics.AddRange( + compilationInfo + .UseMiddlewareTInfos.SelectMany(m => m.DiagnosticInfos) + .Select(d => d.ToDiagnostic()) + ); return diagnostics; } - - private static Diagnostic[] GenerateKeyedServiceKeyDiagnostics( - this EquatableArray methodNameInfos - ) => - methodNameInfos - .SelectMany(onShutdownInvocationInfo => - onShutdownInvocationInfo.DelegateInfo.Parameters - ) - .Where(parameterInfo => parameterInfo.KeyedServiceKey is { DisplayValue: null }) - .Select(parameterInfo => - Diagnostic.Create( - Diagnostics.InvalidAttributeArgument, - parameterInfo.KeyedServiceKey!.Value.LocationInfo?.ToLocation(), - parameterInfo.KeyedServiceKey.Value.Type - ) - ) - .ToArray(); } diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticInfo.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticInfo.cs new file mode 100644 index 00000000..dee169c3 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticInfo.cs @@ -0,0 +1,40 @@ +using LayeredCraft.SourceGeneratorTools.Utilities; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.Models; + +internal sealed record DiagnosticInfo( + DiagnosticDescriptor DiagnosticDescriptor, + LocationInfo? LocationInfo = null, + params object?[] MessageArgs +) +{ + public bool Equals(DiagnosticInfo? other) => + other is not null + && Equals(DiagnosticDescriptor.Id, other.DiagnosticDescriptor.Id) + && Equals(LocationInfo, other.LocationInfo); + + public override int GetHashCode() => HashCode.Combine(DiagnosticDescriptor, LocationInfo); +} + +internal static class DiagnosticInfoExtensions +{ + extension(DiagnosticInfo diagnosticInfo) + { + internal static DiagnosticInfo Create( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo, + object?[] messageArgs + ) => new(diagnosticDescriptor, locationInfo, messageArgs); + + internal Diagnostic ToDiagnostic() => + Diagnostic.Create( + diagnosticInfo.DiagnosticDescriptor, + diagnosticInfo.LocationInfo?.ToLocation(), + diagnosticInfo.MessageArgs + ); + + internal void ReportDiagnostic(SourceProductionContext context) => + context.ReportDiagnostic(diagnosticInfo.ToDiagnostic()); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs new file mode 100644 index 00000000..fe071c68 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Diagnostics/DiagnosticResult.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Models; + +// ReSharper disable MemberCanBePrivate.Global + +namespace MinimalLambda.SourceGenerators; + +internal sealed class DiagnosticResult +{ + internal bool IsSuccess { get; } + internal T? Value { get; } + internal DiagnosticInfo? Error { get; } + + private DiagnosticResult(bool isSuccess, T? value, DiagnosticInfo? error) + { + IsSuccess = isSuccess; + Value = value; + Error = error; + } + + public static DiagnosticResult Success(T value) => new(true, value, null); + + public static implicit operator DiagnosticResult(T value) => Success(value); + + public static DiagnosticResult Failure(DiagnosticInfo error) => new(false, default, error); + + public static DiagnosticResult Failure( + DiagnosticDescriptor diagnosticDescriptor, + LocationInfo? locationInfo = null, + params object?[] messageArgs + ) => new(false, default, new DiagnosticInfo(diagnosticDescriptor, locationInfo, messageArgs)); + + public DiagnosticResult Map(Func map) => + IsSuccess + ? DiagnosticResult.Success(map(Value!)) + : DiagnosticResult.Failure(Error!); + + public DiagnosticResult Bind(Func> bind) => + IsSuccess ? bind(Value!) : DiagnosticResult.Failure(Error!); + + public TResult Match( + Func onSuccess, + Func onFailure + ) => IsSuccess ? onSuccess(Value!) : onFailure(Error!); + + public void Switch(Action onSuccess, Action onFailure) + { + if (IsSuccess) + onSuccess(Value!); + else + onFailure(Error!); + } +} diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs similarity index 54% rename from src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs rename to src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs index dc182be3..a643b72d 100644 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/LambdaHostOutputGenerator.cs +++ b/src/MinimalLambda.SourceGenerators/Emitters/MinimalLambdaEmitter.cs @@ -1,30 +1,22 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; using MinimalLambda.SourceGenerators.Models; -namespace MinimalLambda.SourceGenerators; +namespace MinimalLambda.SourceGenerators.Emitters; -internal static class LambdaHostOutputGenerator +internal static class MinimalLambdaEmitter { - internal static string GeneratedCodeAttribute + internal static readonly Lazy GeneratedCodeAttribute = new(() => { - get - { - if (field is null) - { - var assembly = Assembly.GetExecutingAssembly(); - var generatorName = assembly.GetName().Name; - var generatorVersion = assembly.GetName().Version.ToString(); - - field = - $"""[global::System.CodeDom.Compiler.GeneratedCode("{generatorName}", "{generatorVersion}")]"""; - } + var assembly = Assembly.GetExecutingAssembly(); + var generatorName = assembly.GetName().Name; + var generatorVersion = assembly.GetName().Version; - return field; - } - } + return $"""[global::System.CodeDom.Compiler.GeneratedCode("{generatorName}", "{generatorVersion}")]"""; + }); internal static void Generate(SourceProductionContext context, CompilationInfo compilationInfo) { @@ -41,7 +33,10 @@ internal static void Generate(SourceProductionContext context, CompilationInfo c List outputs = [ - CommonSources.Generate(), + TemplateHelper.Render( + GeneratorConstants.InterceptsLocationAttributeTemplateFile, + new { GeneratedCodeAttribute } + ), """ namespace MinimalLambda.Generated { @@ -59,37 +54,50 @@ namespace MinimalLambda.Generated // if MapHandler calls found, generate the source code. if (compilationInfo.MapHandlerInvocationInfos.Count >= 1) outputs.Add( - MapHandlerSources.Generate( - compilationInfo.MapHandlerInvocationInfos, - compilationInfo.BuilderInfos + TemplateHelper.Render( + GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile, + new + { + GeneratedCodeAttribute, + MapHandlerCalls = compilationInfo.MapHandlerInvocationInfos, + } ) ); // add UseMiddleware interceptors if (compilationInfo.UseMiddlewareTInfos.Count >= 1) - outputs.Add(UseMiddlewareTSource.Generate(compilationInfo.UseMiddlewareTInfos)); + outputs.Add( + TemplateHelper.Render( + GeneratorConstants.UseMiddlewareTTemplateFile, + new { GeneratedCodeAttribute, Calls = compilationInfo.UseMiddlewareTInfos } + ) + ); // add OnInit interceptors if (compilationInfo.OnInitInvocationInfos.Count >= 1) outputs.Add( - GenericHandlerSources.Generate( - compilationInfo.OnInitInvocationInfos, - "OnInit", - "bool", - "true", - "ILambdaOnInitBuilder" + TemplateHelper.Render( + GeneratorConstants.GenericHandlerTemplateFile, + new + { + Name = compilationInfo.OnInitInvocationInfos.First().MethodType, + Calls = compilationInfo.OnInitInvocationInfos, + GeneratedCodeAttribute, + } ) ); // add OnShutdown interceptors if (compilationInfo.OnShutdownInvocationInfos.Count >= 1) outputs.Add( - GenericHandlerSources.Generate( - compilationInfo.OnShutdownInvocationInfos, - "OnShutdown", - null, - null, - "ILambdaOnShutdownBuilder" + TemplateHelper.Render( + GeneratorConstants.GenericHandlerTemplateFile, + new + { + Name = compilationInfo.OnShutdownInvocationInfos.First().MethodType, + Calls = compilationInfo.OnShutdownInvocationInfos, + GeneratedCodeAttribute, + } ) ); diff --git a/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs new file mode 100644 index 00000000..f45c3403 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Emitters/TemplateHelper.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Reflection; +using Scriban; + +namespace MinimalLambda.SourceGenerators.Emitters; + +/// +/// Helper class for loading, caching, and rendering Scriban templates from embedded +/// resources. +/// +internal static class TemplateHelper +{ + private static readonly ConcurrentDictionary Cache = new(); + + /// + /// Renders a Scriban template with the provided model. Templates are cached after first load + /// for performance. + /// + /// The type of the model to render + /// + /// Relative path to the template resource (e.g., + /// "Templates.Common.InterceptsLocationAttribute.scriban") + /// + /// The model object to render with the template + /// Rendered template as a string + /// Thrown if template is not found or has parsing errors + internal static string Render(string resourceName, TModel model) + { + var template = Cache.GetOrAdd(resourceName, LoadTemplate); + return template.Render(model); + } + + /// Loads a Scriban template from embedded resources. + /// + /// Relative path to the template resource (e.g., + /// "Templates.Common.InterceptsLocationAttribute.scriban") + /// + /// Parsed Scriban template ready for rendering + /// Thrown if template is not found or has parsing errors + private static Template LoadTemplate(string relativePath) + { + var assembly = Assembly.GetExecutingAssembly(); + var baseName = assembly.GetName().Name; + + // Convert relative path to resource name format + var templateName = relativePath + .TrimStart('.') + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + + // Find the manifest resource name that ends with our template name + var manifestTemplateName = assembly + .GetManifestResourceNames() + .FirstOrDefault(x => x.EndsWith(templateName, StringComparison.InvariantCulture)); + + if (string.IsNullOrEmpty(manifestTemplateName)) + { + var availableResources = string.Join(", ", assembly.GetManifestResourceNames()); + throw new InvalidOperationException( + $"Did not find required resource ending in '{templateName}' in assembly '{baseName}'. " + + $"Available resources: {availableResources}" + ); + } + + // Load the template content + using var stream = assembly.GetManifestResourceStream(manifestTemplateName); + if (stream == null) + throw new FileNotFoundException( + $"Template '{relativePath}' not found in embedded resources. " + + $"Manifest resource name: '{manifestTemplateName}'" + ); + + using var reader = new StreamReader(stream); + var templateContent = reader.ReadToEnd(); + + // Parse and validate the template + var template = Template.Parse(templateContent, relativePath); + if (!template.HasErrors) + return template; + var errors = string.Join( + "\n", + template.Messages.Select(m => + $"{relativePath}({m.Span.Start.Line},{m.Span.Start.Column}): {m.Message}" + ) + ); + throw new InvalidOperationException( + $"Failed to parse template '{relativePath}':\n{errors}" + ); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs deleted file mode 100644 index c9d968c0..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/DelegateInfoExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using System.Text; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators.Extensions; - -internal static class DelegateInfoExtensions -{ - extension(DelegateInfo delegateInfo) - { - internal string BuildHandlerCastCall() - { - var signatureBuilder = new StringBuilder(); - signatureBuilder.Append("Utilities.Cast(handler, "); - - signatureBuilder.Append(delegateInfo.ReturnTypeInfo.FullyQualifiedType); - - signatureBuilder.Append(" ("); - - signatureBuilder.Append( - string.Join( - ", ", - delegateInfo.Parameters.Select( - (p, i) => - $"{p.TypeInfo.FullyQualifiedType} arg{i}{(p.IsOptional ? " = default" : "")}" - ) - ) - ); - - signatureBuilder.Append(") => throw null!)"); - - var handlerSignature = signatureBuilder.ToString(); - - return handlerSignature; - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs new file mode 100644 index 00000000..6677974b --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/EnumerableExtensions.cs @@ -0,0 +1,38 @@ +using System.Linq; +using MinimalLambda.SourceGenerators; +using MinimalLambda.SourceGenerators.Models; + +namespace System.Collections.Generic; + +internal static class EnumerableExtensions +{ + extension(IEnumerable enumerable) + { + public void ForEach(Action action) + { + foreach (var item in enumerable) + action(item); + } + + internal (List Data, List Diagnostics) CollectDiagnosticResults( + Func> extractor + ) => + enumerable + .Select(extractor) + .Aggregate( + ( + Successes: new List(enumerable is ICollection c ? c.Count : 0), + Diagnostics: new List() + ), + static (acc, result) => + { + result.Switch( + info => acc.Successes.Add(info), + diagnostic => acc.Diagnostics.Add(diagnostic) + ); + + return acc; + } + ); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs new file mode 100644 index 00000000..da86fc43 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/FunctionalExtensions.cs @@ -0,0 +1,15 @@ +namespace System; + +internal static class FunctionalExtensions +{ + extension(T source) + { + public TResult Map(Func func) => func(source); + + public T Tap(Action action) + { + action(source); + return source; + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs new file mode 100644 index 00000000..58f1f66a --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/IncrementalValueProviderExtensions.cs @@ -0,0 +1,11 @@ +namespace Microsoft.CodeAnalysis; + +internal static class IncrementalValueProviderExtensions +{ + extension(IncrementalValuesProvider valueProviders) + where T : class + { + public IncrementalValuesProvider WhereNotNull() => + valueProviders.Where(static v => v is not null).Select(static (v, _) => v!); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs new file mode 100644 index 00000000..740e2521 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/MethodSymbolExtensions.cs @@ -0,0 +1,122 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using MinimalLambda.SourceGenerators; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace Microsoft.CodeAnalysis; + +internal static class MethodSymbolExtensions +{ + extension(IMethodSymbol methodSymbol) + { + internal string GetCastableSignature() + { + var returnType = methodSymbol.ReturnType.QualifiedNullableName; + var parameters = methodSymbol + .Parameters.Select( + (p, i) => + { + var type = p.Type.QualifiedNullableName; + var defaultValue = p.IsOptional ? " = default" : ""; + return $"{type} arg{i}{defaultValue}"; + } + ) + .ToArray(); + var parameterList = string.Join(", ", parameters); + + return $"{returnType} ({parameterList}) => throw null!"; + } + + internal bool IsAwaitable(GeneratorContext context) + { + if (methodSymbol.ReturnType is not INamedTypeSymbol namedTypeSymbol) + return false; + + var returnType = namedTypeSymbol.ConstructedFrom; + + // Check for Task and Task + var task = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task); + if (returnType.Equals(task, SymbolEqualityComparer.Default)) + return true; + + var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); + if (returnType.Equals(taskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for ValueTask and ValueTask + var valueTask = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask + ); + if (returnType.Equals(valueTask, SymbolEqualityComparer.Default)) + return true; + + var valueTaskOfT = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask_T + ); + if (returnType.OriginalDefinition.Equals(valueTaskOfT, SymbolEqualityComparer.Default)) + return true; + + // Check for custom awaitable pattern (has GetAwaiter method) + return returnType + .GetMembers("GetAwaiter") + .OfType() + .Any(m => m.Parameters.Length == 0 && !m.IsStatic); + } + + internal bool HasMeaningfulReturnType( + GeneratorContext context, + [NotNullWhen(true)] out INamedTypeSymbol? unwrappedReturnType + ) + { + unwrappedReturnType = null; + + if (methodSymbol.ReturnType is not INamedTypeSymbol namedTypeSymbol) + return false; + + if (IsVoidLike(namedTypeSymbol.ConstructedFrom)) + { + unwrappedReturnType = namedTypeSymbol; + return false; + } + + if (methodSymbol.UnwrapReturnType(context) is not INamedTypeSymbol namedTypeSymbol2) + return false; + + unwrappedReturnType = namedTypeSymbol2; + return true; + + bool IsVoidLike(ITypeSymbol type) => + context.WellKnownTypes.IsType( + type, + WellKnownType.System_Void, + WellKnownType.System_Threading_Tasks_Task, + WellKnownType.System_Threading_Tasks_ValueTask + ); + } + + private ITypeSymbol UnwrapReturnType(GeneratorContext context) + { + if (methodSymbol.ReturnType is not INamedTypeSymbol namedReturnType) + return methodSymbol.ReturnType; + + var taskOfT = context.WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task_T); + var valueTaskOfT = context.WellKnownTypes.Get( + WellKnownType.System_Threading_Tasks_ValueTask_T + ); + + var originalDef = namedReturnType.OriginalDefinition; + + if ( + ( + originalDef.Equals(taskOfT, SymbolEqualityComparer.Default) + || originalDef.Equals(valueTaskOfT, SymbolEqualityComparer.Default) + ) + && namedReturnType.TypeArguments.Length > 0 + ) + return namedReturnType.TypeArguments[0]; + + return namedReturnType; + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs new file mode 100644 index 00000000..17eb5cef --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/ParameterSymbolExtensions.cs @@ -0,0 +1,140 @@ +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using MinimalLambda.SourceGenerators; +using MinimalLambda.SourceGenerators.Extensions; +using MinimalLambda.SourceGenerators.Models; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace Microsoft.CodeAnalysis; + +internal static class ParameterSymbolExtensions +{ + internal static bool IsDecoratedWithAttribute( + this IParameterSymbol parameterSymbol, + GeneratorContext context, + params WellKnownType[] attributeType + ) => + parameterSymbol + .GetAttributes() + .Any(a => + a.AttributeClass is not null + && context.WellKnownTypes.IsType(a.AttributeClass, attributeType) + ); + + extension(IParameterSymbol parameterSymbol) + { + internal bool IsFromEvent(GeneratorContext context) => + parameterSymbol + .GetAttributes() + .Any(attribute => + attribute.AttributeClass is not null + && context.WellKnownTypes.IsType( + attribute.AttributeClass, + WellKnownType.MinimalLambda_Builder_EventAttribute, + WellKnownType.MinimalLambda_Builder_FromEventAttribute + ) + ); + + internal DiagnosticResult<(string Assignment, string? Key)> GetDiParameterAssignment( + GeneratorContext context + ) + { + var paramType = parameterSymbol.Type.QualifiedNullableName; + + var isRequired = + parameterSymbol.IsOptional + || parameterSymbol.NullableAnnotation == NullableAnnotation.Annotated; + + return parameterSymbol + .IsFromKeyedService(context) + .Bind<(string, string?)>(result => + result.IsKeyed + ? ( + isRequired + ? $"context.ServiceProvider.GetKeyedService<{paramType}>({result.Key})" + : $"context.ServiceProvider.GetRequiredKeyedService<{paramType}>({result.Key})", + result.Key + ) + : ( + isRequired + ? $"context.ServiceProvider.GetService<{paramType}>()" + : $"context.ServiceProvider.GetRequiredService<{paramType}>()", + null + ) + ); + } + + private DiagnosticResult<(bool IsKeyed, string? Key)> IsFromKeyedService( + GeneratorContext context + ) => + parameterSymbol + .GetAttributes() + .FirstOrDefault(attribute => + attribute is { AttributeClass: not null } + && context.WellKnownTypes.IsType( + attribute.AttributeClass, + WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute + ) + ) + ?.ExtractKeyedServiceKey() + .Bind<(bool, string?)>(key => (true, key)) + ?? (false, null); + } + + extension(AttributeData attributeData) + { + private DiagnosticResult ExtractKeyedServiceKey() + { + var argument = attributeData.ConstructorArguments[0]; + + if (argument.IsNull) + return "null"; + + object? value = null; + try + { + value = argument.Value; + } + catch + { + // ignore + } + + if (value is null) + return DiagnosticResult.Failure( + MinimalLambda.SourceGenerators.Diagnostics.InvalidAttributeArgument, + attributeData.GetAttributeArgumentLocation(0), + argument.Type?.QualifiedNullableName + ); + + return argument.Kind switch + { + TypedConstantKind.Primitive when value is string strValue => + CSharp.SymbolDisplay.FormatLiteral(strValue, true), + + TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", + + TypedConstantKind.Primitive when value is bool boolValue => boolValue + ? "true" + : "false", + + TypedConstantKind.Primitive or TypedConstantKind.Enum => + $"({argument.Type?.QualifiedNullableName}){value}", + + TypedConstantKind.Type when value is ITypeSymbol typeValue => + $"typeof({typeValue.QualifiedNullableName})", + + _ => value.ToString(), + }; + } + + private LocationInfo? GetAttributeArgumentLocation(int index) => + attributeData.ApplicationSyntaxReference?.GetSyntax() + is AttributeSyntax { ArgumentList: { } argumentList } + ? argumentList + .Arguments.ElementAtOrDefault(index) + ?.Expression.GetLocation() + .ToLocationInfo() + : null; + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs deleted file mode 100644 index 79ac3c52..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace MinimalLambda.SourceGenerators; - -internal static class SyntaxExtensions -{ - internal static bool TryGetMethodName( - this SyntaxNode node, - [NotNullWhen(true)] out string? methodName - ) - { - methodName = null; - if ( - node is InvocationExpressionSyntax - { - Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: var method }, - } - ) - { - methodName = method; - return true; - } - - return false; - } - - internal static bool IsGeneratedFile(this SyntaxNode node) => - node.SyntaxTree.FilePath.EndsWith(".g.cs"); -} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 00000000..6107bfde --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MinimalLambda.SourceGenerators; + +internal static class SyntaxNodeExtensions +{ + extension(SyntaxNode node) + { + internal bool TryGetMethodName([NotNullWhen(true)] out string? methodName) + { + methodName = null; + if ( + node is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax + { + Name.Identifier.ValueText: var method, + }, + } + ) + { + methodName = method; + return true; + } + + return false; + } + + internal bool IsGeneratedFile() => node.SyntaxTree.FilePath.EndsWith(".g.cs"); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs deleted file mode 100644 index 36a218e6..00000000 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeExtractorExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace MinimalLambda.SourceGenerators.Extensions; - -internal static class TypeExtractorExtensions -{ - private static readonly SymbolDisplayFormat Format = - SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( - SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier - ); - - internal static string GetAsGlobal(this ITypeSymbol typeSymbol, TypeSyntax? typeSyntax = null) - { - var baseTypeName = typeSymbol.ToDisplayString(Format); - - return typeSyntax is NullableTypeSyntax && !baseTypeName.EndsWith("?") - ? baseTypeName + "?" - : baseTypeName; - } -} diff --git a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs index 2231e75f..e8d810f9 100644 --- a/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs +++ b/src/MinimalLambda.SourceGenerators/Extensions/TypeSymbolExtensions.cs @@ -1,71 +1,23 @@ using Microsoft.CodeAnalysis; -namespace MinimalLambda.SourceGenerators; +namespace MinimalLambda.SourceGenerators.Extensions; internal static class TypeSymbolExtensions { - extension(ITypeSymbol typeSymbol) - { - internal bool IsTypeAwaitable() => typeSymbol.IsTask() || typeSymbol.IsValueTask(); - - internal bool IsTask() => - typeSymbol.Name == "Task" - && typeSymbol.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; - - internal bool IsValueTask() => - typeSymbol.Name == "ValueTask" - && typeSymbol.ContainingNamespace?.ToDisplayString() == "System.Threading.Tasks"; - - internal string GetTypeKind() - { - // Check if it's an interface - if (typeSymbol.TypeKind == TypeKind.Interface) - return "interface"; - - // Check if it's a class - if (typeSymbol.TypeKind == TypeKind.Class) - { - // Check if it's abstract - if (typeSymbol.IsAbstract && typeSymbol.IsSealed) - return "static class"; - - if (typeSymbol.IsAbstract) - return "abstract class"; - - if (typeSymbol.IsSealed) - return "sealed class"; + private static readonly SymbolDisplayFormat NullableFormat = + SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier + ); - if (typeSymbol.IsRecord) - return "record class"; + private static readonly SymbolDisplayFormat NotNullableFormat = + SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.ExpandNullable + ); - return "class"; - } - - // Check if it's a struct - if (typeSymbol.TypeKind == TypeKind.Struct) - { - if (typeSymbol.IsRecord) - return "record struct"; - - if (typeSymbol.IsReadOnly) - return "readonly struct"; - - if (typeSymbol.IsRefLikeType) - return "ref struct"; - - return "struct"; - } - - // Check if it's an enum - if (typeSymbol.TypeKind == TypeKind.Enum) - return "enum"; - - // Check if it's a delegate - if (typeSymbol.TypeKind == TypeKind.Delegate) - return "delegate"; + extension(ITypeSymbol typeSymbol) + { + internal string QualifiedName => typeSymbol.ToDisplayString(NotNullableFormat); - // Other types - return typeSymbol.TypeKind.ToString().ToLower(); - } + internal string QualifiedNullableName => typeSymbol.ToDisplayString(NullableFormat); } } diff --git a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs index 6bfb933d..ae0e2a6c 100644 --- a/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs +++ b/src/MinimalLambda.SourceGenerators/GeneratorConstants.cs @@ -1,66 +1,14 @@ namespace MinimalLambda.SourceGenerators; -/// Constants for common .NET and AWS Lambda types used in source generation. -internal static class TypeConstants -{ - internal const string ILambdaContext = "global::Amazon.Lambda.Core.ILambdaContext"; - - internal const string ILambdaInvocationContext = - "global::MinimalLambda.ILambdaInvocationContext"; - - internal const string ILambdaLifecycleContext = "global::MinimalLambda.ILambdaLifecycleContext"; - - internal const string IDisposable = "global::System.IDisposable"; - - internal const string IAsyncDisposable = "global::System.IAsyncDisposable"; - - internal const string CancellationToken = "global::System.Threading.CancellationToken"; - - internal const string Task = "global::System.Threading.Tasks.Task"; - - internal const string ValueTask = "global::System.Threading.Tasks.ValueTask"; - - internal const string TaskBool = "global::System.Threading.Tasks.Task"; - - internal const string Void = "void"; - - internal const string Action = "global::System.Action"; - - internal const string Func = "global::System.Func"; - - internal const string Stream = "global::System.IO.Stream"; - - internal const string IServiceProvider = "global::System.IServiceProvider"; -} - /// Constants for attribute names used in source generation. internal static class AttributeConstants { - internal const string EventAttribute = "MinimalLambda.Builder.EventAttribute"; - - internal const string FromEventAttribute = "MinimalLambda.Builder.FromEventAttribute"; - - internal const string FromKeyedService = - "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute"; - internal const string MiddlewareConstructor = "MinimalLambda.Builder.MiddlewareConstructorAttribute"; - - internal const string FromArguments = "MinimalLambda.Builder.FromArgumentsAttribute"; - - internal const string FromServices = "MinimalLambda.Builder.FromServicesAttribute"; } internal static class GeneratorConstants { - internal const string MapHandlerMethodName = "MapHandler"; - - internal const string OnShutdownMethodName = "OnShutdown"; - - internal const string OnInitMethodName = "OnInit"; - - internal const string UseOpenTelemetryTracingMethodName = "UseOpenTelemetryTracing"; - internal const string InterceptsLocationAttributeTemplateFile = "Templates/InterceptsLocationAttribute.scriban"; @@ -70,7 +18,4 @@ internal static class GeneratorConstants internal const string UseMiddlewareTTemplateFile = "Templates/UseMiddlewareT.scriban"; internal const string GenericHandlerTemplateFile = "Templates/GenericHandler.scriban"; - - internal const string LambdaHostUseOpenTelemetryTracingExtensionsTemplateFile = - "Templates/OpenTelemetry.scriban"; } diff --git a/src/MinimalLambda.SourceGenerators/GeneratorContext.cs b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs new file mode 100644 index 00000000..c5556178 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/GeneratorContext.cs @@ -0,0 +1,34 @@ +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators; + +internal class GeneratorContext +{ + internal WellKnownTypes.WellKnownTypes WellKnownTypes { get; } + internal CancellationToken CancellationToken { get; } + internal SemanticModel SemanticModel { get; } + internal GeneratorSyntaxContext GeneratorAttributeSyntaxContext { get; } + + internal SyntaxNode Node { get; } + + internal GeneratorContext(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + GeneratorAttributeSyntaxContext = context; + Node = context.Node; + SemanticModel = GeneratorAttributeSyntaxContext.SemanticModel; + CancellationToken = cancellationToken; + WellKnownTypes = SourceGenerators.WellKnownTypes.WellKnownTypes.GetOrCreate( + context.SemanticModel.Compilation + ); + } +} + +internal static class GeneratorContextExtensions +{ + extension(GeneratorContext context) + { + public void ThrowIfCancellationRequested() => + context.CancellationToken.ThrowIfCancellationRequested(); + } +} diff --git a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs b/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs deleted file mode 100644 index 19dabb57..00000000 --- a/src/MinimalLambda.SourceGenerators/MapHandlerIncrementalGenerator.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -[Generator] -public class MapHandlerIncrementalGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // Language version gate - only generate source if C# 11 or later is used - var csharpSufficient = context.CompilationProvider.Select( - static (compilation, _) => - compilation - is CSharpCompilation - { - LanguageVersion: LanguageVersion.Default or >= LanguageVersion.CSharp11, - } - ); - - context.RegisterSourceOutput( - csharpSufficient, - static (spc, ok) => - { - if (!ok) - spc.ReportDiagnostic( - Diagnostic.Create(Diagnostics.CSharpVersionTooLow, Location.None) - ); - } - ); - - // Find all MapHandler method calls with lambda analysis - var mapHandlerCalls = context - .SyntaxProvider.CreateSyntaxProvider( - MapHandlerSyntaxProvider.Predicate, - MapHandlerSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // Find all OnShutdown method calls with lambda analysis - var onShutdownCalls = context - .SyntaxProvider.CreateSyntaxProvider( - OnShutdownSyntaxProvider.Predicate, - OnShutdownSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // Find all OnInit method calls with lambda analysis - var onInitCalls = context - .SyntaxProvider.CreateSyntaxProvider( - OnInitSyntaxProvider.Predicate, - OnInitSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // find LambdaApplicationBuilder.Build() calls - var lambdaApplicationBuilderBuildCalls = context - .SyntaxProvider.CreateSyntaxProvider( - LambdaApplicationBuilderBuildSyntaxProvider.Predicate, - LambdaApplicationBuilderBuildSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // find UseMiddleware() calls - var useMiddlewareTCalls = context - .SyntaxProvider.CreateSyntaxProvider( - UseMiddlewareTSyntaxProvider.Predicate, - UseMiddlewareTSyntaxProvider.Transformer - ) - .Where(static m => m is not null) - .Select(static (m, _) => m!.Value); - - // collect call - var mapHandlerCallsCollected = mapHandlerCalls.Collect(); - var onShutdownCallsCollected = onShutdownCalls.Collect(); - var onInitCallsCollected = onInitCalls.Collect(); - var lambdaApplicationBuilderBuildCallsCollected = - lambdaApplicationBuilderBuildCalls.Collect(); - var useMiddlewareTCallsCollected = useMiddlewareTCalls.Collect(); - - // combine the compilation and map handler calls - var combined = mapHandlerCallsCollected - .Combine(onShutdownCallsCollected) - .Combine(onInitCallsCollected) - .Combine(lambdaApplicationBuilderBuildCallsCollected) - .Combine(useMiddlewareTCallsCollected) - .Select( - CompilationInfo? (t, _) => - { - if ( - t.Left.Left.Left.Left.Length == 0 - && t.Left.Left.Left.Right.Length == 0 - && t.Left.Left.Right.Length == 0 - && t.Left.Right.Length == 0 - && t.Right.Length == 0 - ) - return null; - - return new CompilationInfo( - t.Left.Left.Left.Left.ToEquatableArray(), - t.Left.Left.Left.Right.ToEquatableArray(), - t.Left.Left.Right.ToEquatableArray(), - t.Left.Right.ToEquatableArray(), - t.Right.ToEquatableArray() - ); - } - ); - - // Generate source when calls are found - context.RegisterSourceOutput( - combined, - (productionContext, info) => - { - if (info is null) - return; - - LambdaHostOutputGenerator.Generate(productionContext, info.Value); - } - ); - } -} diff --git a/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs new file mode 100644 index 00000000..69eda7fd --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/MinimalLambdaGenerator.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using MinimalLambda.SourceGenerators.Emitters; +using MinimalLambda.SourceGenerators.Models; + +namespace MinimalLambda.SourceGenerators; + +[Generator] +public class MinimalLambdaGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Language version gate - only generate source if C# 11 or later is used + var csharpSufficient = context.CompilationProvider.Select( + static (compilation, _) => + compilation + is CSharpCompilation + { + LanguageVersion: LanguageVersion.Default or >= LanguageVersion.CSharp11, + } + ); + + context.RegisterSourceOutput( + csharpSufficient, + static (spc, ok) => + { + if (!ok) + spc.ReportDiagnostic( + Diagnostic.Create(Diagnostics.CSharpVersionTooLow, Location.None) + ); + } + ); + + // handler registration calls + var registrationCalls = context + .SyntaxProvider.CreateSyntaxProvider( + HandlerSyntaxProvider.Predicate, + HandlerSyntaxProvider.Transformer + ) + .WhereNotNull(); + + // find UseMiddleware() calls + var useMiddlewareTCalls = context + .SyntaxProvider.CreateSyntaxProvider( + UseMiddlewareTSyntaxProvider.Predicate, + UseMiddlewareTSyntaxProvider.Transformer + ) + .WhereNotNull(); + + var registrationCallsCollected = registrationCalls.Collect(); + var useMiddlewareTCallsCollected = useMiddlewareTCalls.Collect(); + + // combine the compilation and map handler calls + var combined = registrationCallsCollected + .Combine(useMiddlewareTCallsCollected) + .Select( + CompilationInfo? (t, _) => + { + var (handlerInfos, useMiddlewareInfo) = t; + + if (handlerInfos.Length == 0 && useMiddlewareInfo.Length == 0) + return null; + + return new CompilationInfo + { + MapHandlerInvocationInfos = handlerInfos + .OfType() + .ToEquatableArray(), + OnShutdownInvocationInfos = handlerInfos + .OfType() + .Where(h => h.MethodType == MethodType.OnShutdown) + .ToEquatableArray(), + OnInitInvocationInfos = handlerInfos + .OfType() + .Where(h => h.MethodType == MethodType.OnInit) + .ToEquatableArray(), + UseMiddlewareTInfos = useMiddlewareInfo.ToEquatableArray(), + }; + } + ); + + // Generate source when calls are found + context.RegisterSourceOutput( + combined, + (productionContext, info) => + { + if (info is null) + return; + + MinimalLambdaEmitter.Generate(productionContext, info.Value); + } + ); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs deleted file mode 100644 index 3b1c1f17..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/ArgumentInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct ArgumentInfo(string? Type, string? Name); diff --git a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs deleted file mode 100644 index d2181d41..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/ClassInfo.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Extensions; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct ClassInfo( - string GloballyQualifiedName, - string ShortName, - EquatableArray ConstructorInfos, - EquatableArray ImplementedInterfaces, - string TypeKind -); - -internal static class ClassInfoExtensions -{ - extension(ClassInfo classInfo) - { - internal static ClassInfo Create(ITypeSymbol typeSymbol) - { - var typeKind = typeSymbol.GetTypeKind(); - - // get the globally qualified name of the class - var globallyQualifiedName = typeSymbol.GetAsGlobal(); - - // get short name - var shortName = typeSymbol.Name; - - // handle each instance constructor on the type - var constructorInfo = ((INamedTypeSymbol)typeSymbol) - .InstanceConstructors.Select(MethodInfo.Create) - .ToEquatableArray(); - - // get all interfaces - var interfaceNames = typeSymbol - .AllInterfaces.Select(i => i.GetAsGlobal()) - .ToEquatableArray(); - - return new ClassInfo( - globallyQualifiedName, - shortName, - constructorInfo, - interfaceNames, - typeKind - ); - } - - internal bool IsInterfaceImplemented(string interfaceName) => - classInfo.ImplementedInterfaces.Any(i => i == interfaceName); - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs deleted file mode 100644 index 4f45970f..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/CompilationInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using LayeredCraft.SourceGeneratorTools.Types; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct CompilationInfo( - EquatableArray MapHandlerInvocationInfos, - EquatableArray OnShutdownInvocationInfos, - EquatableArray OnInitInvocationInfos, - EquatableArray BuilderInfos, - EquatableArray UseMiddlewareTInfos -); diff --git a/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs b/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs deleted file mode 100644 index f31d9f77..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/DelegateInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct DelegateInfo( - EquatableArray Parameters, - bool IsAwaitable, - bool IsAsync, - TypeInfo ReturnTypeInfo -) -{ - internal readonly ParameterInfo? EventParameter = GetEventParameter(Parameters); - - internal string DelegateType => - ReturnTypeInfo.FullyQualifiedType == TypeConstants.Void - ? TypeConstants.Action - : TypeConstants.Func; - - internal bool HasAnyKeyedServiceParameter => - Parameters.Any(p => p.Source == ParameterSource.KeyedService); - - internal bool HasEventParameter => EventParameter is not null; - - internal bool HasResponse => - ReturnTypeInfo.FullyQualifiedType - is not (TypeConstants.Void or TypeConstants.Task or TypeConstants.ValueTask); - - private static ParameterInfo? GetEventParameter(EquatableArray parameters) => - parameters - .Where(p => p.Source == ParameterSource.Event) - .Select(p => (ParameterInfo?)p) - .FirstOrDefault(); -} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/IMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/IMethodInfo.cs new file mode 100644 index 00000000..8cd089ca --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/IMethodInfo.cs @@ -0,0 +1,9 @@ +using LayeredCraft.SourceGeneratorTools.Types; + +namespace MinimalLambda.SourceGenerators.Models; + +internal interface IMethodInfo +{ + EquatableArray DiagnosticInfos { get; } + MethodType MethodType { get; } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleHandlerParameterInfo.cs new file mode 100644 index 00000000..f37eeb1a --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleHandlerParameterInfo.cs @@ -0,0 +1,81 @@ +using Microsoft.CodeAnalysis; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record LifecycleHandlerParameterInfo( + string Assignment, + string InfoComment, + bool IsFromKeyedService, + LocationInfo? LocationInfo, + ParameterSource Source, + string? KeyedServicesKey +); + +internal static class LifecycleHandlerParameterInfoExtensions +{ + extension(LifecycleHandlerParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameter, + GeneratorContext context + ) + { + var parameterInfo = new LifecycleHandlerParameterInfo( + IsFromKeyedService: false, + LocationInfo: LocationInfo.Create(parameter), + Assignment: string.Empty, + InfoComment: string.Empty, + KeyedServicesKey: string.Empty, + Source: ParameterSource.Services + ); + + // context + if ( + context.WellKnownTypes.IsType( + parameter.Type, + WellKnownType.MinimalLambda_ILambdaLifecycleContext + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context", + Source = ParameterSource.Context, + } + ); + + // cancellation token + if ( + context.WellKnownTypes.IsType( + parameter.Type, + WellKnownType.System_Threading_CancellationToken + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context.CancellationToken", + Source = ParameterSource.CancellationToken, + } + ); + + // default assignment from Di + return parameter + .GetDiParameterAssignment(context) + .Bind(diInfo => + DiagnosticResult.Success( + parameterInfo with + { + Assignment = diInfo.Assignment, + IsFromKeyedService = diInfo.Key is not null, + Source = diInfo.Key is not null + ? ParameterSource.KeyedServices + : ParameterSource.Services, + KeyedServicesKey = diInfo.Key, + } + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleMethodInfo.cs new file mode 100644 index 00000000..f5eb1e76 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/LifecycleMethodInfo.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.WellKnownTypes; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record LifecycleMethodInfo( + string InterceptableLocationAttribute, + string DelegateCastType, + EquatableArray DiagnosticInfos, + EquatableArray ParameterAssignments, + bool ShouldAwait, + MethodType MethodType, + string HandleResponseAssignment, + string HandleReturningFromMethod, + string ReturnType, + bool HasAnyFromKeyedServices +) : IMethodInfo; + +internal static class LifecycleMethodInfoExtensions +{ + extension(LifecycleMethodInfo) + { + internal static LifecycleMethodInfo CreateForInit( + IMethodSymbol methodSymbol, + GeneratorContext context + ) + { + var handlerCastType = methodSymbol.GetCastableSignature(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => LifecycleHandlerParameterInfo.Create(parameter, context) + ); + + var isAwaitable = methodSymbol.IsAwaitable(context); + + var hasResponse = methodSymbol.HasMeaningfulReturnType( + context, + out var unwrappedReturnType + ); + + var unwrappedReturnIsBool = + hasResponse + && context.WellKnownTypes.IsType( + unwrappedReturnType!, + WellKnownTypeData.WellKnownType.System_Boolean + ); + + /* + * Return rules: + * If handler returns `Task`, no need to await, can be returned on its own + * If handler returns `ValueTask`, must be awaited and then returned + * If handler returns `bool`, it doesn't need to be awaited and can be returned as + * result default + async, return true default, return Task.FromResult(true); + */ + + var returnIsTaskBool = + methodSymbol.ReturnType is INamedTypeSymbol namedTypeSymbol + && context.WellKnownTypes.IsType( + namedTypeSymbol.ConstructedFrom, + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task_T + ) + && unwrappedReturnIsBool; + + var shouldAwait = isAwaitable && !returnIsTaskBool; + + var handleResponseAssignment = + hasResponse && unwrappedReturnIsBool ? "var response = " : string.Empty; + + var handleReturningFromMethod = hasResponse switch + { + true when returnIsTaskBool || (unwrappedReturnIsBool && isAwaitable) => + "return response;", + true when unwrappedReturnIsBool => "return Task.FromResult(response);", + _ when isAwaitable => "return true;", + _ => "return Task.FromResult(true);", + }; + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + + return new LifecycleMethodInfo( + MethodType: MethodType.OnInit, + InterceptableLocationAttribute: interceptableLocation.Attribute, + DelegateCastType: handlerCastType, + DiagnosticInfos: diagnostics.ToEquatableArray(), + ParameterAssignments: assignments.ToEquatableArray(), + ShouldAwait: shouldAwait, + HandleResponseAssignment: handleResponseAssignment, + HandleReturningFromMethod: handleReturningFromMethod, + ReturnType: "Task", + HasAnyFromKeyedServices: hasAnyKeyedServices + ); + } + + internal static LifecycleMethodInfo CreateForShutdown( + IMethodSymbol methodSymbol, + GeneratorContext context + ) + { + var handlerCastType = methodSymbol.GetCastableSignature(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => LifecycleHandlerParameterInfo.Create(parameter, context) + ); + + var isAwaitable = methodSymbol.IsAwaitable(context); + + var returnIsTask = context.WellKnownTypes.IsType( + methodSymbol.ReturnType, + WellKnownTypeData.WellKnownType.System_Threading_Tasks_Task + ); + + var shouldAwait = isAwaitable && !returnIsTask; + + var handleResponseAssignment = returnIsTask ? "var response = " : string.Empty; + + var handleReturningFromMethod = shouldAwait switch + { + _ when returnIsTask => "return response;", + true => string.Empty, + _ => "return Task.CompletedTask;", + }; + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + + return new LifecycleMethodInfo( + MethodType: MethodType.OnShutdown, + InterceptableLocationAttribute: interceptableLocation.Attribute, + DelegateCastType: handlerCastType, + DiagnosticInfos: diagnostics.ToEquatableArray(), + ParameterAssignments: assignments.ToEquatableArray(), + ShouldAwait: shouldAwait, + HandleResponseAssignment: handleResponseAssignment, + HandleReturningFromMethod: handleReturningFromMethod, + ReturnType: "Task", + HasAnyFromKeyedServices: hasAnyKeyedServices + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerMethodInfo.cs new file mode 100644 index 00000000..8f1d853a --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerMethodInfo.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record MapHandlerMethodInfo( + string InterceptableLocationAttribute, + string DelegateCastType, + EquatableArray ParameterAssignments, + bool IsAwaitable, + bool HasResponse, + bool IsResponseTypeStream, + bool IsEventTypeStream, + bool HasEvent, + string? EventType, + string? UnwrappedResponseType, + bool HasAnyFromKeyedServices, + EquatableArray DiagnosticInfos, + MethodType MethodType = MethodType.MapHandler +) : IMethodInfo; + +internal static class MapHandlerMethodInfoExtensions +{ + private static IEnumerable ReportMultipleEvents( + IEnumerable assignments, + GeneratorContext context + ) + { + var eventAttribute = new Lazy(() => + context + .WellKnownTypes.Get(WellKnownType.MinimalLambda_Builder_FromEventAttribute) + .QualifiedNullableName + ); + + return assignments + .Where(a => a.IsEvent) + .Skip(1) + .Select(a => + DiagnosticInfo.Create( + Diagnostics.MultipleParametersUseAttribute, + a.LocationInfo, + [eventAttribute.Value] + ) + ); + } + + extension(MapHandlerMethodInfo) + { + internal static MapHandlerMethodInfo Create( + IMethodSymbol methodSymbol, + GeneratorContext context + ) + { + var handlerCastType = methodSymbol.GetCastableSignature(); + + if (!InterceptableLocationInfo.TryGet(context, out var interceptableLocation)) + throw new InvalidOperationException("Unable to get interceptable location"); + + var (assignments, diagnostics) = methodSymbol.Parameters.CollectDiagnosticResults( + parameter => MapHandlerParameterInfo.Create(parameter, context) + ); + + // add parameter diagnostics + diagnostics.AddRange(ReportMultipleEvents(assignments, context)); + + var isAwaitable = methodSymbol.IsAwaitable(context); + + var hasResponse = methodSymbol.HasMeaningfulReturnType( + context, + out var unwrappedReturnType + ); + + var isReturnTypeStream = + hasResponse + && context.WellKnownTypes.IsType( + methodSymbol.ReturnType, + WellKnownType.System_IO_Stream + ); + + var hasEvent = assignments.Any(a => a.IsEvent); + + var eventType = hasEvent + ? assignments.Where(a => a.IsEvent).Select(a => a.GloballyQualifiedType).First() + : null; + + var isEventTypeStream = + hasEvent && assignments.Any(a => a is { IsEvent: true, IsStream: true }); + + var hasAnyKeyedServices = assignments.Any(a => a is { IsFromKeyedService: true }); + + return new MapHandlerMethodInfo( + MethodType: MethodType.MapHandler, + InterceptableLocationAttribute: interceptableLocation.Attribute, + DelegateCastType: handlerCastType, + ParameterAssignments: assignments.ToEquatableArray(), + IsAwaitable: isAwaitable, + HasResponse: hasResponse, + IsResponseTypeStream: isReturnTypeStream, + IsEventTypeStream: isEventTypeStream, + HasEvent: hasEvent, + EventType: eventType, + UnwrappedResponseType: unwrappedReturnType?.QualifiedNullableName, + HasAnyFromKeyedServices: hasAnyKeyedServices, + DiagnosticInfos: diagnostics.ToEquatableArray() + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerParameterInfo.cs new file mode 100644 index 00000000..4942e399 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/MapHandlerParameterInfo.cs @@ -0,0 +1,106 @@ +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record MapHandlerParameterInfo( + string GloballyQualifiedType, + bool IsStream, + string Assignment, + string InfoComment, + bool IsEvent, + bool IsFromKeyedService, + LocationInfo? LocationInfo, + ParameterSource Source, + string? KeyedServicesKey +); + +internal static class MapHandlerParameterInfoExtensions +{ + extension(MapHandlerParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameter, + GeneratorContext context + ) + { + var paramType = parameter.Type.QualifiedNullableName; + + var parameterInfo = new MapHandlerParameterInfo( + parameter.Type.QualifiedNullableName, + context.WellKnownTypes.IsType(parameter.Type, WellKnownType.System_IO_Stream), + IsEvent: false, + IsFromKeyedService: false, + LocationInfo: LocationInfo.Create(parameter), + Assignment: string.Empty, + InfoComment: string.Empty, + KeyedServicesKey: string.Empty, + Source: ParameterSource.Services + ); + + // event + if (parameter.IsFromEvent(context)) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = parameterInfo.IsStream + // stream event + ? "context.Features.GetRequired().EventStream" + // non stream event + : $"context.GetRequiredEvent<{paramType}>()", + IsEvent = true, + Source = ParameterSource.Event, + } + ); + + // context + if ( + context.WellKnownTypes.IsType( + parameter.Type, + WellKnownType.Amazon_Lambda_Core_ILambdaContext, + WellKnownType.MinimalLambda_ILambdaInvocationContext + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context", + Source = ParameterSource.Context, + } + ); + + // cancellation token + if ( + context.WellKnownTypes.IsType( + parameter.Type, + WellKnownType.System_Threading_CancellationToken + ) + ) + return DiagnosticResult.Success( + parameterInfo with + { + Assignment = "context.CancellationToken", + Source = ParameterSource.CancellationToken, + } + ); + + // default assignment from Di + return parameter + .GetDiParameterAssignment(context) + .Bind(diInfo => + DiagnosticResult.Success( + parameterInfo with + { + Assignment = diInfo.Assignment, + IsFromKeyedService = diInfo.Key is not null, + Source = diInfo.Key is not null + ? ParameterSource.KeyedServices + : ParameterSource.Services, + KeyedServicesKey = diInfo.Key, + } + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Handlers/MethodType.cs b/src/MinimalLambda.SourceGenerators/Models/Handlers/MethodType.cs new file mode 100644 index 00000000..2ab84812 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Handlers/MethodType.cs @@ -0,0 +1,8 @@ +namespace MinimalLambda.SourceGenerators.Models; + +internal enum MethodType +{ + MapHandler, + OnInit, + OnShutdown, +} diff --git a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs deleted file mode 100644 index 5a34ffa9..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/HigherOrderMethodInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Immutable; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct HigherOrderMethodInfo( - string Name, - DelegateInfo DelegateInfo, - LocationInfo? LocationInfo, - InterceptableLocationInfo InterceptableLocationInfo, - ImmutableArray ArgumentsInfos -); diff --git a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs deleted file mode 100644 index 0ecd39eb..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/InterceptableLocationInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct InterceptableLocationInfo( - int Version, - string Data, - string DisplayLocation -) -{ - internal static InterceptableLocationInfo CreateFrom( - InterceptableLocation interceptableLocation - ) => - new( - interceptableLocation.Version, - interceptableLocation.Data, - interceptableLocation.GetDisplayLocation() - ); -} diff --git a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs b/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs deleted file mode 100644 index ccc5085d..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/KeyedServiceKeyInfo.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using MinimalLambda.SourceGenerators.Extensions; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct KeyedServiceKeyInfo( - string? DisplayValue, - string? Type, - string? BaseType, - LocationInfo? LocationInfo -) -{ - internal static KeyedServiceKeyInfo Create(AttributeData attribute) - { - var (key, keyType, keyBaseType) = ExtractKeyedServiceKey(attribute.ConstructorArguments[0]); - - var keyedServiceKeyInfo = new KeyedServiceKeyInfo(key, keyType, keyBaseType, null); - - // conditionally get location info only if a key is null as a diagnostic needs to be - // provided in that case. - if ( - key is null - && attribute.ApplicationSyntaxReference?.GetSyntax() - is AttributeSyntax { ArgumentList: { Arguments.Count: > 0 } argumentList } - ) - { - var argument = argumentList.Arguments[0]; - var location = argument.Expression.GetLocation(); - var locationInfo = location.CreateLocationInfo(); - return keyedServiceKeyInfo with { LocationInfo = locationInfo }; - } - - return keyedServiceKeyInfo; - } - - internal string ToPublicString() => - $"{nameof(KeyedServiceKeyInfo)} {{ " - + $"{nameof(DisplayValue)} = {DisplayValue}, " - + $"{nameof(Type)} = {Type}, " - + $"{nameof(BaseType)} = {BaseType} }}"; - - private static (string? Key, string? KeyType, string? KeyBaseType) ExtractKeyedServiceKey( - TypedConstant argument - ) - { - var keyBaseType = argument.Type?.BaseType?.GetAsGlobal(); - var keyType = argument.Type?.GetAsGlobal(); - - if (argument.IsNull) - return ("null", keyType, keyBaseType); - - object? value; - try - { - value = argument.Value; - } - catch - { - return (null, keyType, keyBaseType); - } - - if (value is null) - return (null, keyType, keyBaseType); - - // Generate the literal C# code to recreate this value - var keyLiteral = argument.Kind switch - { - TypedConstantKind.Primitive when value is string strValue => - SymbolDisplay.FormatLiteral(strValue, true), - - TypedConstantKind.Primitive when value is char charValue => $"'{charValue}'", - - TypedConstantKind.Primitive when value is bool boolValue => boolValue - ? "true" - : "false", - - TypedConstantKind.Primitive or TypedConstantKind.Enum => - $"({argument.Type?.GetAsGlobal()}){value}", - - TypedConstantKind.Type when value is ITypeSymbol typeValue => - $"typeof({typeValue.GetAsGlobal()})", - - _ => value.ToString(), - }; - - return (keyLiteral, keyType, keyBaseType); - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs deleted file mode 100644 index 873f578e..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/MethodInfo.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct MethodInfo( - int ArgumentCount, - EquatableArray AttributeInfos, - EquatableArray Parameters -); - -internal static class ConstructorInfoExtensions -{ - extension(MethodInfo) - { - internal static MethodInfo Create(IMethodSymbol constructor) - { - var attributeInfos = constructor - .GetAttributes() - .Where(a => a.AttributeClass is not null) - .Select(AttributeInfo.Create) - .ToEquatableArray(); - - var parameterInfos = constructor - .Parameters.Select(ParameterInfo.Create) - .ToEquatableArray(); - - return new MethodInfo(parameterInfos.Count, attributeInfos, parameterInfos); - } - } -} - -internal readonly record struct AttributeInfo(LocationInfo? LocationInfo, string FullName); - -internal static class AttributeInfoExtensions -{ - extension(AttributeInfo) - { - internal static AttributeInfo Create(AttributeData attributeData) - { - var syntax = attributeData.ApplicationSyntaxReference?.GetSyntax(); - var location = syntax?.GetLocation(); - var locationData = location?.CreateLocationInfo(); - - var name = attributeData.AttributeClass?.ToString() ?? "UNKNOWN"; - - return new AttributeInfo(locationData, name); - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs new file mode 100644 index 00000000..e8503fb3 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareClassInfo.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record MiddlewareClassInfo( + string GloballyQualifiedName, + string ShortName, + EquatableArray ParameterInfos, + bool ImplementsDisposable, + bool ImplementsAsyncDisposable, + bool AllParametersFromServices +); + +internal static class MiddlewareExtensions +{ + extension(MiddlewareClassInfo) + { + internal static (MiddlewareClassInfo? Info, List Diagnostics) Create( + INamedTypeSymbol typeSymbol, + Location? location, + GeneratorContext context + ) + { + // validate that middleware class is a concrete -> not interface or abstract class + if (typeSymbol.TypeKind == TypeKind.Interface || typeSymbol.IsAbstract) + return ( + null, + [ + DiagnosticInfo.Create( + Diagnostics.MustBeConcreteType, + location?.ToLocationInfo(), + [typeSymbol.QualifiedName] + ), + ] + ); + + List diagnostics = []; + + // get the globally qualified name of the class + var globallyQualifiedName = typeSymbol.QualifiedNullableName; + + // get short name, i.e., not qualified + var shortName = typeSymbol.Name; + + // get constructor + var constructor = GetConstructor(typeSymbol, context) + .Map(result => + { + diagnostics.AddRange(result.DiagnosticInfos); + return result.MethodSymbol; + }); + + // get constructor parameters + var parameterInfos = constructor is not null + ? constructor + .Parameters.CollectDiagnosticResults(parameter => + MiddlewareParameterInfo.Create(parameter, context) + ) + .Map(results => + { + diagnostics.AddRange(results.Diagnostics); + return results.Data; + }) + : []; + + // implements IDisposable + var implementsIDisposable = typeSymbol.AllInterfaces.Any(i => + context.WellKnownTypes.IsType(i, WellKnownType.System_IDisposable) + ); + + // implements IAsyncDisposable + var implementsIAsyncDisposable = typeSymbol.AllInterfaces.Any(i => + context.WellKnownTypes.IsType(i, WellKnownType.System_IAsyncDisposable) + ); + + // are all parameters for the constructor from services + var allParametersFromServices = parameterInfos.All(p => p.FromServices); + + return ( + new MiddlewareClassInfo( + globallyQualifiedName, + shortName, + parameterInfos.ToEquatableArray(), + implementsIDisposable, + implementsIAsyncDisposable, + allParametersFromServices + ), + diagnostics + ); + } + } + + private static (IMethodSymbol? MethodSymbol, DiagnosticInfo[] DiagnosticInfos) GetConstructor( + INamedTypeSymbol namedTypeSymbol, + GeneratorContext context + ) + { + // 1. Get constructors annotated with `[MiddlewareConstructor]` + var constructors = namedTypeSymbol + .InstanceConstructors.Where(c => + c.GetAttributes() + .Any(a => + a.AttributeClass is not null + && context.WellKnownTypes.IsType( + a.AttributeClass, + WellKnownType.MinimalLambda_Builder_MiddlewareConstructorAttribute + ) + ) + ) + .ToArray(); + + return constructors.Length switch + { + // if more than one found, we will return diagnostics + > 1 => ( + MethodSymbol: null, + DiagnosticInfos: constructors + .Skip(1) + .Select(c => + DiagnosticInfo.Create( + Diagnostics.MultipleConstructorsWithAttribute, + c.Locations.FirstOrDefault()?.ToLocationInfo(), + [AttributeConstants.MiddlewareConstructor] + ) + ) + .ToArray() + ), + + // return single constructor that has an `[MiddlewareConstructor]` attribute + 1 => (MethodSymbol: constructors.FirstOrDefault(), DiagnosticInfos: []), + + // 2. default to constructor with most parameters + _ => ( + MethodSymbol: namedTypeSymbol + .InstanceConstructors.OrderByDescending(c => c.Parameters.Length) + .First(), + DiagnosticInfos: [] + ), + }; + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareParameterInfo.cs new file mode 100644 index 00000000..3ad20474 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/MiddlewareParameterInfo.cs @@ -0,0 +1,76 @@ +using Microsoft.CodeAnalysis; +using MinimalLambda.SourceGenerators.Extensions; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record MiddlewareParameterInfo( + string Name, + string GloballyQualifiedType, + string GloballyQualifiedNotNullableType, + bool FromArguments, + bool FromServices, + string FromServicesAssignment, + string InfoComment, + ParameterSource ServiceSource, + string? KeyedServicesKey +); + +internal static class MiddlewareParameterInfoExtensions +{ + extension(MiddlewareParameterInfo) + { + internal static DiagnosticResult Create( + IParameterSymbol parameterSymbol, + GeneratorContext context + ) + { + context.ThrowIfCancellationRequested(); + + // parameter name + var name = parameterSymbol.Name; + + // globally qualified type + var globallyQualifiedType = parameterSymbol.Type.QualifiedNullableName; + + // globally qualified type - not nullable + var globallyQualifiedNotNullableType = parameterSymbol.Type.QualifiedName; + + // determine if it has a `[FromArguments]` attribute + var fromArguments = parameterSymbol.IsDecoratedWithAttribute( + context, + WellKnownType.MinimalLambda_Builder_FromArgumentsAttribute + ); + + // determine if it has a `[FromServices]` attribute + var fromServices = + !fromArguments + && parameterSymbol.IsDecoratedWithAttribute( + context, + WellKnownType.MinimalLambda_Builder_FromServicesAttribute, + WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute + ); + + // assignment from services + return parameterSymbol + .GetDiParameterAssignment(context) + .Bind(diInfo => + DiagnosticResult.Success( + new MiddlewareParameterInfo( + InfoComment: "", + Name: name, + GloballyQualifiedType: globallyQualifiedType, + GloballyQualifiedNotNullableType: globallyQualifiedNotNullableType, + FromArguments: fromArguments, + FromServices: fromServices, + FromServicesAssignment: diInfo.Assignment, + ServiceSource: diInfo.Key is not null + ? ParameterSource.KeyedServices + : ParameterSource.Services, + KeyedServicesKey: diInfo.Key + ) + ) + ); + } + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs new file mode 100644 index 00000000..6fd7ee1c --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Middleware/UseMiddlewareTInfo.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LayeredCraft.SourceGeneratorTools.Types; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace MinimalLambda.SourceGenerators.Models; + +internal record UseMiddlewareTInfo( + string? InterceptableLocationAttribute, + MiddlewareClassInfo? ClassInfo, + EquatableArray DiagnosticInfos +); + +internal static class UseMiddlewareTInfoExtensions +{ + extension(UseMiddlewareTInfo) + { + internal static UseMiddlewareTInfo Create( + IInvocationOperation invocationOperation, + GeneratorContext context + ) + { + if ( + invocationOperation.Syntax + is not InvocationExpressionSyntax invocationExpressionSyntax + ) + throw new InvalidOperationException("Syntax is not InvocationExpressionSyntax"); + + List diagnosticInfos = []; + + var interceptableLocation = ( + context.SemanticModel.GetInterceptableLocation( + invocationExpressionSyntax, + context.CancellationToken + ) + ?? throw new InvalidOperationException( + "Interceptable location is null (Should not happen)" + ) + ) + .ToInterceptableLocationInfo() + .Attribute; + + var middlewareClassType = invocationOperation + .TargetMethod.TypeArguments.FirstOrDefault() + .Map(typeSymbol => + typeSymbol as INamedTypeSymbol + ?? throw new InvalidOperationException( + "Middleware class type is not INamedTypeSymbol (Should not happen)" + ) + ); + + TryGetLocationInfo(invocationExpressionSyntax, out var typeArgumentLocation); + + var classInfo = MiddlewareClassInfo + .Create(middlewareClassType, typeArgumentLocation, context) + .Map(result => + { + diagnosticInfos.AddRange(result.Diagnostics); + return result.Info; + }); + + return new UseMiddlewareTInfo( + interceptableLocation, + classInfo, + diagnosticInfos.ToEquatableArray() + ); + } + } + + private static bool TryGetLocationInfo( + InvocationExpressionSyntax invocationExpressionSyntax, + out Location? locationInfo + ) + { + locationInfo = null; + if ( + invocationExpressionSyntax is + { Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName } } + ) + { + var typeArgument = genericName.TypeArgumentList.Arguments[0]; + locationInfo = typeArgument.GetLocation(); + return true; + } + + return false; + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs b/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs deleted file mode 100644 index 74a4ae92..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/ParameterInfo.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using Microsoft.CodeAnalysis; - -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct ParameterInfo( - string Name, - TypeInfo TypeInfo, - LocationInfo? LocationInfo, - ParameterSource Source, - KeyedServiceKeyInfo? KeyedServiceKey, - bool IsNullable, - bool IsOptional, - EquatableArray AttributeNames -) -{ - internal bool IsRequired => !IsOptional && !IsNullable; - - internal static ParameterInfo Create(IParameterSymbol parameter) - { - var typeInfo = TypeInfo.Create(parameter.Type); - var name = parameter.Name; - var location = parameter.CreateLocationInfo(); - var (source, keyedService) = GetSourceFromAttribute( - parameter.GetAttributes(), - typeInfo.FullyQualifiedType - ); - var isNullable = parameter.NullableAnnotation == NullableAnnotation.Annotated; - var isOptional = parameter.IsOptional; - var attributeNames = parameter - .GetAttributes() - .Where(a => a.AttributeClass is not null) - .Select(a => a.AttributeClass!.ToString()) - .ToEquatableArray(); - - return new ParameterInfo( - name, - typeInfo, - location, - source, - keyedService, - isNullable, - isOptional, - attributeNames - ); - } - - internal string ToPublicString() => - $"{nameof(ParameterInfo)} {{ " - + $"Type = {TypeInfo.FullyQualifiedType}, " - + $"{nameof(Name)} = {Name}, " - + $"{nameof(Source)} = {Source}, " - + $"{nameof(IsNullable)} = {IsNullable}, " - + $"{nameof(IsOptional)} = {IsOptional}" - + $"{(KeyedServiceKey.HasValue ? ", " + KeyedServiceKey.Value.ToPublicString() + " " : "")}}}"; - - private static ( - ParameterSource Source, - KeyedServiceKeyInfo? KeyedServiceKey - ) GetSourceFromAttribute(IEnumerable attributes, string type) - { - // try and extract source from attributes - foreach (var attribute in attributes) - switch (attribute.AttributeClass?.ToString()) - { - case AttributeConstants.EventAttribute: - case AttributeConstants.FromEventAttribute: - return (ParameterSource.Event, null); - - case AttributeConstants.FromKeyedService: - var keyedServiceKey = KeyedServiceKeyInfo.Create(attribute); - return (ParameterSource.KeyedService, keyedServiceKey); - } - - // fallback to get source from type - return type switch - { - TypeConstants.CancellationToken => (ParameterSource.CancellationToken, null), - TypeConstants.ILambdaContext => (ParameterSource.HostContext, null), - TypeConstants.ILambdaInvocationContext => (ParameterSource.HostContext, null), - TypeConstants.ILambdaLifecycleContext => (ParameterSource.LifecycleContext, null), - _ => (ParameterSource.Service, null), - }; - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/Shared/CompilationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/CompilationInfo.cs new file mode 100644 index 00000000..3862f513 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Shared/CompilationInfo.cs @@ -0,0 +1,10 @@ +using LayeredCraft.SourceGeneratorTools.Types; + +namespace MinimalLambda.SourceGenerators.Models; + +internal readonly record struct CompilationInfo( + EquatableArray MapHandlerInvocationInfos, + EquatableArray OnShutdownInvocationInfos, + EquatableArray OnInitInvocationInfos, + EquatableArray UseMiddlewareTInfos +); diff --git a/src/MinimalLambda.SourceGenerators/Models/Shared/InterceptableLocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/InterceptableLocationInfo.cs new file mode 100644 index 00000000..3550eae6 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/Models/Shared/InterceptableLocationInfo.cs @@ -0,0 +1,53 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MinimalLambda.SourceGenerators.Models; + +internal sealed record InterceptableLocationInfo(int Version, string Data, string DisplayLocation); + +internal static class InterceptableLocationInfoExtensions +{ + extension(InterceptableLocationInfo location) + { + internal static InterceptableLocationInfo CreateFrom( + InterceptableLocation interceptableLocation + ) => + new( + interceptableLocation.Version, + interceptableLocation.Data, + interceptableLocation.GetDisplayLocation() + ); + + internal static bool TryGet( + GeneratorContext context, + [NotNullWhen(true)] out InterceptableLocationInfo? interceptableLocationInfo + ) + { + interceptableLocationInfo = null; + + if (context.Node is not InvocationExpressionSyntax invocationExpr) + return false; + + var interceptableLocation = context.SemanticModel.GetInterceptableLocation( + invocationExpr, + context.CancellationToken + ); + + if (interceptableLocation is null) + return false; + + interceptableLocationInfo = InterceptableLocationInfo.CreateFrom(interceptableLocation); + return true; + } + + internal string Attribute => + $"""[InterceptsLocation({location.Version}, "{location.Data}")]"""; + } + + extension(InterceptableLocation interceptableLocation) + { + internal InterceptableLocationInfo ToInterceptableLocationInfo() => + InterceptableLocationInfo.CreateFrom(interceptableLocation); + } +} diff --git a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/LocationInfo.cs similarity index 54% rename from src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/LocationInfo.cs index 2963b5de..4949383f 100644 --- a/src/MinimalLambda.SourceGenerators/Models/LocationInfo.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Shared/LocationInfo.cs @@ -16,11 +16,26 @@ internal static class LocationInfoExtensions { internal Location ToLocation() => Location.Create(locationInfo.FilePath, locationInfo.TextSpan, locationInfo.LineSpan); + + internal static LocationInfo? Create(Location? location) => + location?.SourceTree is null + ? null + : new LocationInfo( + location.SourceTree.FilePath, + location.SourceSpan, + location.GetLineSpan().Span + ); + + internal static LocationInfo? Create(ISymbol symbol) => + LocationInfo.Create(symbol.Locations.FirstOrDefault()); + + internal static LocationInfo? Create(SyntaxNode syntaxNode) => + LocationInfo.Create(syntaxNode.GetLocation()); } extension(Location location) { - internal LocationInfo? CreateLocationInfo() => + internal LocationInfo? ToLocationInfo() => location.SourceTree is null ? null : new LocationInfo( @@ -33,12 +48,11 @@ location.SourceTree is null extension(ISymbol symbol) { internal LocationInfo? CreateLocationInfo() => - symbol.Locations.FirstOrDefault()?.CreateLocationInfo(); + symbol.Locations.FirstOrDefault()?.ToLocationInfo(); } extension(SyntaxNode syntaxNode) { - internal LocationInfo? CreateLocationInfo() => - syntaxNode.GetLocation().CreateLocationInfo(); + internal LocationInfo? CreateLocationInfo() => syntaxNode.GetLocation().ToLocationInfo(); } } diff --git a/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs b/src/MinimalLambda.SourceGenerators/Models/Shared/ParameterSource.cs similarity index 62% rename from src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs rename to src/MinimalLambda.SourceGenerators/Models/Shared/ParameterSource.cs index 61315319..abec383b 100644 --- a/src/MinimalLambda.SourceGenerators/Models/ParameterSource.cs +++ b/src/MinimalLambda.SourceGenerators/Models/Shared/ParameterSource.cs @@ -3,9 +3,8 @@ namespace MinimalLambda.SourceGenerators.Models; internal enum ParameterSource { Event, - KeyedService, + Context, CancellationToken, - HostContext, - LifecycleContext, - Service, + KeyedServices, + Services, } diff --git a/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs b/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs deleted file mode 100644 index 082379fc..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/SimpleMethodInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct SimpleMethodInfo( - string Name, - LocationInfo? LocationInfo, - InterceptableLocationInfo InterceptableLocationInfo -); diff --git a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs b/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs deleted file mode 100644 index 59d43528..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/TypeInfo.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using MinimalLambda.SourceGenerators.Extensions; - -namespace MinimalLambda.SourceGenerators.Models; - -/// Represents the information associated with a named type in C# source code. -internal readonly record struct TypeInfo( - string FullyQualifiedType, - string? UnwrappedFullyQualifiedType, - bool IsGeneric, - ImmutableArray ImplementedInterfaces -); - -internal static class TypeInfoExtensions -{ - extension(TypeInfo typeInfo) - { - internal static TypeInfo Create(ITypeSymbol typeSymbol, TypeSyntax? syntax = null) - { - var fullyQualifiedType = typeSymbol.GetAsGlobal(syntax); - var unwrappedFullyQualifiedType = typeSymbol.GetUnwrappedFullyQualifiedType(syntax); - var isGeneric = typeSymbol is INamedTypeSymbol { IsGenericType: true }; - var implementedInterfaces = typeSymbol - .AllInterfaces.Select(i => i.GetAsGlobal()) - .ToImmutableArray(); - - return new TypeInfo( - fullyQualifiedType, - unwrappedFullyQualifiedType, - isGeneric, - implementedInterfaces - ); - } - - internal static TypeInfo CreateFullyQualifiedType(string fullyQualifiedType) => - new(fullyQualifiedType, null, false, ImmutableArray.Empty); - } - - extension(ITypeSymbol typeSymbol) - { - /// Gets a fully qualified type name without it being wrapped in Task or ValueTask - private string? GetUnwrappedFullyQualifiedType(TypeSyntax? syntax = null) - { - if ( - typeSymbol is not INamedTypeSymbol namedTypeSymbol - || (!namedTypeSymbol.IsTask() && !namedTypeSymbol.IsValueTask()) - ) - return typeSymbol.GetAsGlobal(syntax); - - // if not generic Task or ValueTask, return null as no wrapped return value - if (!namedTypeSymbol.IsGenericType || namedTypeSymbol.TypeArguments.Length == 0) - return null; - - return namedTypeSymbol.TypeArguments.First().GetAsGlobal(syntax); - } - } -} diff --git a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs b/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs deleted file mode 100644 index 3ff642f3..00000000 --- a/src/MinimalLambda.SourceGenerators/Models/UseMiddlewareTInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MinimalLambda.SourceGenerators.Models; - -internal readonly record struct UseMiddlewareTInfo( - InterceptableLocationInfo InterceptableLocationInfo, - ClassInfo ClassInfo, - LocationInfo? GenericTypeArgumentLocation -); diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs deleted file mode 100644 index 571e4b0d..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/CommonSources.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MinimalLambda.SourceGenerators; - -internal static class CommonSources -{ - internal static string Generate() - { - var model = new { LambdaHostOutputGenerator.GeneratedCodeAttribute }; - - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.InterceptsLocationAttributeTemplateFile - ); - - return template.Render(model); - } -} diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs deleted file mode 100644 index 738531d9..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/GenericHandlerSources.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class GenericHandlerSources -{ - /// - /// Generates C# code for a generic handler. The handler is a wrapper around the actual - /// handler. The return type of the wrapper is a Task or Task<T> depending on - /// the return type of the actual handler. - /// - internal static string Generate( - EquatableArray higherOrderMethodInfos, - string methodName, - string? wrapperReturnType, - string? defaultWrapperReturnValue, - string targetType - ) - { - var calls = higherOrderMethodInfos - .Select(higherOrderMethodInfo => - { - // build handler function signature - var handlerSignature = higherOrderMethodInfo.DelegateInfo.BuildHandlerCastCall(); - - // get arguments for handler - var handlerArgs = - higherOrderMethodInfo.DelegateInfo.BuildHandlerParameterAssignment(); - - // get the return type of the wrapper function wrapped in a Task - var fullWrapperReturnType = wrapperReturnType is not null - ? $"global::System.Threading.Tasks.Task<{wrapperReturnType}>" - : "global::System.Threading.Tasks.Task"; - - // get the return type of the wrapper function wrapped in a Task - shortened - // to just use Task - var shortFullWrapperReturnType = wrapperReturnType is not null - ? $"Task<{wrapperReturnType}>" - : "Task"; - - // should await determined by whether the delegate is awaitable and if the - // delegate - // return type matches the wrapper return type 1:1 - var shouldAwait = - fullWrapperReturnType - != higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - && higherOrderMethodInfo.DelegateInfo.IsAwaitable; - - // should return response - var shouldReturnResponse = - higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - != TypeConstants.Void - && ( - wrapperReturnType - == higherOrderMethodInfo - .DelegateInfo - .ReturnTypeInfo - .UnwrappedFullyQualifiedType - || fullWrapperReturnType - == higherOrderMethodInfo.DelegateInfo.ReturnTypeInfo.FullyQualifiedType - ); - - // should wrap the response in a Task - var shouldWrapResponse = - shouldReturnResponse && !higherOrderMethodInfo.DelegateInfo.IsAwaitable; - - // default return value - var defaultReturnValueString = !shouldAwait - ? defaultWrapperReturnValue is not null - ? $"Task.FromResult({defaultWrapperReturnValue})" - : "Task.CompletedTask" - : defaultWrapperReturnValue; - - return new - { - Location = higherOrderMethodInfo.InterceptableLocationInfo, - WrapperReturnType = shortFullWrapperReturnType, - HandlerSignature = handlerSignature, - ShouldAwait = shouldAwait, - higherOrderMethodInfo.DelegateInfo.HasAnyKeyedServiceParameter, - HandlerArgs = handlerArgs, - ShouldReturnResponse = shouldReturnResponse, - ShouldWrapResponse = shouldWrapResponse, - DefaultReturnValue = defaultReturnValueString, - TargetType = targetType, - }; - }) - .ToArray(); - - var model = new - { - Name = methodName, - Calls = calls, - LambdaHostOutputGenerator.GeneratedCodeAttribute, - }; - - var template = TemplateHelper.LoadTemplate(GeneratorConstants.GenericHandlerTemplateFile); - - var outCode = template.Render(model); - - return outCode; - } - - private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) - { - var handlerArgs = delegateInfo - .Parameters.Select(param => new HandlerArg - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // CancellationToken -> get directly from arguments - ParameterSource.CancellationToken => "context.CancellationToken", - - // ILambdaLifecycleContext -> get directly from arguments - ParameterSource.LifecycleContext => "context", - - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => - $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }) - .ToArray(); - - return handlerArgs; - } - - // ReSharper disable NotAccessedPositionalProperty.Local - private readonly record struct HandlerArg(string String, string Assignment); -} diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs deleted file mode 100644 index 660895f3..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/MapHandlerSources.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class MapHandlerSources -{ - internal static string Generate( - EquatableArray mapHandlerInvocationInfos, - EquatableArray builderInfo - ) - { - var mapHandlerCalls = mapHandlerInvocationInfos.Select(mapHandlerInvocationInfo => - { - var delegateInfo = mapHandlerInvocationInfo.DelegateInfo; - - // build handler function signature - var handlerSignature = delegateInfo.BuildHandlerCastCall(); - - // build out assignment statements for each handler parameter - var handlerArgs = delegateInfo.BuildHandlerParameterAssignment(); - - // get input event type - var inputEvent = delegateInfo.EventParameter is { } p - ? new - { - IsStream = p.TypeInfo.FullyQualifiedType == TypeConstants.Stream, - Type = p.TypeInfo.FullyQualifiedType, - } - : null; - - // get output response type and whether it is a stream - var outputResponse = delegateInfo.HasResponse - ? new - { - ResponseType = delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType, - ResponseIsStream = delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType - == TypeConstants.Stream, - } - : null; - - // determine if event feature is required - var isEventFeatureRequired = inputEvent is { IsStream: false }; - - // determine if response feature is required - var isResponseFeatureRequired = outputResponse is { ResponseIsStream: false }; - - return new - { - Location = mapHandlerInvocationInfo.InterceptableLocationInfo, - HandlerSignature = handlerSignature, - IsEventFeatureRequired = isEventFeatureRequired, - IsResponseFeatureRequired = isResponseFeatureRequired, - delegateInfo.HasAnyKeyedServiceParameter, - HandlerArgs = handlerArgs, - ShouldAwait = delegateInfo.IsAwaitable, - InputEvent = inputEvent, - OutputResponse = outputResponse, - }; - }); - - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.LambdaHostMapHandlerExtensionsTemplateFile - ); - - return template.Render( - new - { - LambdaHostOutputGenerator.GeneratedCodeAttribute, - MapHandlerCalls = mapHandlerCalls, - } - ); - } - - private static HandlerArg[] BuildHandlerParameterAssignment(this DelegateInfo delegateInfo) => - delegateInfo - .Parameters.Select(param => new HandlerArg - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // Event -> deserialize to type - ParameterSource.Event - when param.TypeInfo.FullyQualifiedType == TypeConstants.Stream => - "context.Features.GetRequired().EventStream", - - ParameterSource.Event => - $"context.GetRequiredEvent<{param.TypeInfo.FullyQualifiedType}>()", - - // ILambdaContext OR ILambdaInvocationContext -> use context - // directly - ParameterSource.HostContext => "context", - - // CancellationToken -> get from context - ParameterSource.CancellationToken => "context.CancellationToken", - - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => - $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }) - .ToArray(); - - // ReSharper disable NotAccessedPositionalProperty.Local - private readonly record struct HandlerArg(string String, string Assignment); -} diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs deleted file mode 100644 index 958c9f45..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/OpenTelemetrySources.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OpenTelemetrySources -{ - internal static string Generate( - EquatableArray useOpenTelemetryTracingInfos, - DelegateInfo delegateInfo, - string generatedCodeAttribute - ) - { - // get the handler input event type - var eventType = delegateInfo.EventParameter is { } p ? p.TypeInfo.FullyQualifiedType : null; - - // get the handler output return type - var responseType = delegateInfo.HasResponse - ? delegateInfo.ReturnTypeInfo.UnwrappedFullyQualifiedType - : null; - - // interceptable locations - var locations = useOpenTelemetryTracingInfos.Select(u => u.InterceptableLocationInfo); - - var model = new - { - Locations = locations, - EventType = eventType, - ResponseType = responseType, - GeneratedCodeAttribute = generatedCodeAttribute, - }; - - var template = TemplateHelper.LoadTemplate( - GeneratorConstants.LambdaHostUseOpenTelemetryTracingExtensionsTemplateFile - ); - - return template.Render(model); - } -} diff --git a/src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs b/src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs deleted file mode 100644 index b4f70c58..00000000 --- a/src/MinimalLambda.SourceGenerators/OutputGenerators/UseMiddlewareTSource.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Linq; -using LayeredCraft.SourceGeneratorTools.Types; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class UseMiddlewareTSource -{ - internal static string Generate(EquatableArray useMiddlewareTInfos) - { - var useMiddlewareTCalls = useMiddlewareTInfos.Select(useMiddlewareTInfo => - { - var classInfo = useMiddlewareTInfo.ClassInfo; - - // choose what constructor to use with the following criteria: - // 1. if it has a `[MiddlewareConstructor]` attribute. Multiple of these are not - // valid. - // 2. default to the constructor with the most arguments - var constructor = classInfo - .ConstructorInfos.Select(c => (MethodInfo?)c) - .FirstOrDefault(c => - c!.Value.AttributeInfos.Any(a => - a.FullName == AttributeConstants.MiddlewareConstructor - ) - ); - - constructor ??= classInfo - .ConstructorInfos.OrderByDescending(c => c.ArgumentCount) - .First(); - - var parameters = constructor - .Value.Parameters.Select(p => - { - var fromArgs = p.AttributeNames.Any(n => n == AttributeConstants.FromArguments); - - // From services is defined as either having a `[FromServices]` - // attribute or a - // `[FromKeyedServices]` attribute - var fromServices = p.AttributeNames.Any(n => - n is AttributeConstants.FromServices or AttributeConstants.FromKeyedService - ); - - var paramAssignment = p.BuildParameterAssignment(); - - var fullyQualifiedTypeNotNull = - p.TypeInfo.FullyQualifiedType.RemoveTrailingChar("?"); - - return new - { - p.TypeInfo.FullyQualifiedType, - FullyQualifiedTypeNotNull = fullyQualifiedTypeNotNull, - p.Name, - FromArguments = fromArgs, - FromServices = fromServices, - paramAssignment.Assignment, - paramAssignment.String, - }; - }) - .ToArray(); - - var isDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( - TypeConstants.IDisposable - ); - - var isAsyncDisposable = useMiddlewareTInfo.ClassInfo.IsInterfaceImplemented( - TypeConstants.IAsyncDisposable - ); - - var allFromServices = parameters.All(p => p.FromServices); - - return new - { - Location = useMiddlewareTInfo.InterceptableLocationInfo, - FullMiddlewareClassName = classInfo.GloballyQualifiedName, - ShortMiddlewareClassName = classInfo.ShortName, - AllFromServices = allFromServices, - Parameters = parameters, - AnyParameters = parameters.Length > 0, - IsDisposable = isDisposable, - IsAsyncDisposable = isAsyncDisposable, - }; - }); - - var template = TemplateHelper.LoadTemplate(GeneratorConstants.UseMiddlewareTTemplateFile); - - return template.Render( - new { LambdaHostOutputGenerator.GeneratedCodeAttribute, Calls = useMiddlewareTCalls } - ); - } - - private static ParameterArg BuildParameterAssignment(this ParameterInfo param) => - new() - { - String = param.ToPublicString(), - Assignment = param.Source switch - { - // inject keyed service from the DI container - required - ParameterSource.KeyedService when param.IsRequired => - $"context.ServiceProvider.GetRequiredKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // inject keyed service from the DI container - optional - ParameterSource.KeyedService => - $"context.ServiceProvider.GetKeyedService<{param.TypeInfo.FullyQualifiedType}>({param.KeyedServiceKey?.DisplayValue})", - - // default: inject service from the DI container - required - _ when param.IsRequired => - $"context.ServiceProvider.GetRequiredService<{param.TypeInfo.FullyQualifiedType}>()", - - // default: inject service from the DI container - optional - _ => $"context.ServiceProvider.GetService<{param.TypeInfo.FullyQualifiedType}>()", - }, - }; - - private static string RemoveTrailingChar(this string value, string trailing) => - value.EndsWith(trailing) ? value[..^1] : value; - - private readonly record struct ParameterArg(string String, string Assignment); -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs deleted file mode 100644 index 58eba2f2..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/Extractors/HandlerInfoExtractor.cs +++ /dev/null @@ -1,379 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using MinimalLambda.SourceGenerators.Extensions; -using MinimalLambda.SourceGenerators.Models; -using TypeInfo = MinimalLambda.SourceGenerators.Models.TypeInfo; - -namespace MinimalLambda.SourceGenerators; - -using TypeInfo = TypeInfo; - -internal static class HandlerInfoExtractor -{ - internal static bool Predicate(SyntaxNode node, params string[] methodNames) => - !node.IsGeneratedFile() - && node.TryGetMethodName(out var name) - && methodNames.Contains(name); - - internal static HigherOrderMethodInfo? Transformer( - GeneratorSyntaxContext context, - Func delegateFilter, - CancellationToken cancellationToken - ) - { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); - - if ( - operation - is not IInvocationOperation - { - TargetMethod.ContainingNamespace: - { - Name: "Builder", - ContainingNamespace: - { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, - }, - } targetOperation - || targetOperation.TargetMethod.ContainingAssembly.Name != "MinimalLambda" - ) - return null; - - if (context.Node is not InvocationExpressionSyntax invocationExpr) - return null; - - var handler = invocationExpr.ArgumentList.Arguments.ElementAtOrDefault(0)?.Expression; - - var delegateInfo = handler?.ExtractDelegateInfo(context, cancellationToken); - if (delegateInfo is null) - return null; - - // filter out non-generic shutdown method calls - if (delegateFilter(delegateInfo.Value)) - return null; - - // get method arguments - var argumentInfos = targetOperation - .Arguments.Select(argument => - { - var typeAsGlobal = argument.Value.Type?.GetAsGlobal(); - var parameterName = argument.Parameter?.Name; - - return new ArgumentInfo(typeAsGlobal, parameterName); - }) - .ToImmutableArray(); - - // get interceptable location - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - invocationExpr, - cancellationToken - )!; - - return new HigherOrderMethodInfo( - targetOperation.TargetMethod.Name, - LocationInfo: context.Node.CreateLocationInfo(), - DelegateInfo: delegateInfo.Value, - InterceptableLocationInfo: InterceptableLocationInfo.CreateFrom(interceptableLocation), - ArgumentsInfos: argumentInfos - ); - } - - private static DelegateInfo? ExtractDelegateInfo( - this ExpressionSyntax handler, - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) - { - // setup list of mutator functions - List updaters = []; - - // if we are dealing with a cast expression, set up a mutator to update the delegate type - if (handler is CastExpressionSyntax castExpression) - { - var del = GetDelegateFromCast(castExpression, cancellationToken); - if (del is null) - return null; - - handler = del; - - updaters.Add(UpdateTypesFromCast(context, castExpression)); - } - - var result = handler switch - { - IdentifierNameSyntax or MemberAccessExpressionSyntax => ExtractInfoFromDelegate( - context, - handler, - cancellationToken - ), - - LambdaExpressionSyntax lambda => ExtractInfoFromLambda( - context, - lambda, - cancellationToken - ), - - _ => null, - }; - - if (result is null) - return null; - - return updaters.Aggregate( - result.Value, - (current, updater) => updater(current, cancellationToken) - ); - } - - private static ExpressionSyntax? GetDelegateFromCast( - CastExpressionSyntax castExpression, - CancellationToken _ - ) - { - // must have at least 2 children -> expression at index 1, cast at index 0 - var expression = castExpression.ChildNodes().ElementAtOrDefault(1); - if (expression is null) - return null; - - // unwrap parenthesized expressions - while (expression is ParenthesizedExpressionSyntax parenthesizedExpression) - expression = parenthesizedExpression.Expression; - - return expression switch - { - // top level static method - e.g. (Func)Handler - IdentifierNameSyntax identifier => identifier, - - // static method on a class - e.g. (Func)MyClass.Handler - MemberAccessExpressionSyntax memberAccess => memberAccess, - - // parenthesized lambda expression - e.g. (Func)() => 1 - ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda, - - // simple lambda expression - e.g. (Func)x => x + 1 - SimpleLambdaExpressionSyntax simpleLambda => simpleLambda, - - // default, not a supported delegate type - _ => null, - }; - } - - private static Updater UpdateTypesFromCast( - GeneratorSyntaxContext context, - CastExpressionSyntax castExpression - ) => - (delegateInfo, cancellationToken) => - { - var castTypeInfo = ModelExtensions.GetTypeInfo( - context.SemanticModel, - castExpression.Type, - cancellationToken - ); - - if (castTypeInfo.Type is IErrorTypeSymbol) - throw new InvalidOperationException( - $"Failed to resolve type info for {castTypeInfo.Type.ToDisplayString()}." - ); - - if (castTypeInfo.Type is not INamedTypeSymbol namedType) - throw new InvalidOperationException( - $"Cast type must be a named delegate type, but got {castTypeInfo.Type?.ToDisplayString() ?? "null"}." - ); - - var invokeMethod = namedType - .GetMembers("Invoke") - .OfType() - .FirstOrDefault(); - - if (invokeMethod == null) - throw new InvalidOperationException( - $"Cast type {namedType.ToDisplayString()} is not a valid delegate type (missing Invoke method)." - ); - - if (invokeMethod.Parameters.Length != delegateInfo.Parameters.Count) - throw new InvalidOperationException( - $"Parameter count mismatch: cast delegate has {invokeMethod.Parameters.Length} parameters, " - + $"but existing delegate has {delegateInfo.Parameters.Count} parameters." - ); - - var updatedParameters = invokeMethod - .Parameters.Zip( - delegateInfo.Parameters, - (castParam, originalParam) => - originalParam with - { - TypeInfo = TypeInfo.Create(castParam.Type), - LocationInfo = castParam.CreateLocationInfo(), - } - ) - .ToEquatableArray(); - - // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = invokeMethod.ReturnType.GetAsGlobal(); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (invokeMethod.IsAsync || invokeMethod.ReturnType.IsTypeAwaitable()); - - // get response type TypeInfo - var responseTypeInfo = TypeInfo.Create(invokeMethod.ReturnType); - - return new DelegateInfo( - updatedParameters, - isAwaitable, - delegateInfo.IsAsync, - responseTypeInfo - ); - }; - - private static DelegateInfo? ExtractInfoFromDelegate( - GeneratorSyntaxContext context, - ExpressionSyntax delegateExpression, - CancellationToken cancellationToken - ) - { - var symbolInfo = ModelExtensions.GetSymbolInfo( - context.SemanticModel, - delegateExpression, - cancellationToken - ); - - // if a symbol is not found, try to find a candidate symbol as backup - var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); - - if (symbol is not IMethodSymbol methodSymbol) - return null; - - var parameters = methodSymbol - .Parameters.AsEnumerable() - .Select(ParameterInfo.Create) - .ToEquatableArray(); - - // get the fully qualified type that may be wrapped in Task or ValueTask. - var fullResponseType = methodSymbol.ReturnType.GetAsGlobal(); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (methodSymbol.IsAsync || methodSymbol.ReturnType.IsTypeAwaitable()); - - // get response type TypeInfo - var responseTypeInfo = TypeInfo.Create(methodSymbol.ReturnType); - - return new DelegateInfo(parameters, isAwaitable, methodSymbol.IsAsync, responseTypeInfo); - } - - private static DelegateInfo ExtractInfoFromLambda( - GeneratorSyntaxContext context, - LambdaExpressionSyntax lambdaExpression, - CancellationToken cancellationToken - ) - { - var sematicModel = context.SemanticModel; - - var parameterSyntaxes = lambdaExpression switch - { - SimpleLambdaExpressionSyntax simpleLambda => new[] { simpleLambda.Parameter }.Where(p => - p != null - ), - ParenthesizedLambdaExpressionSyntax parenthesizedLambda => - parenthesizedLambda.ParameterList.Parameters.AsEnumerable(), - _ => [], - }; - - // extract parameter information - var parameters = parameterSyntaxes - .Select(p => sematicModel.GetDeclaredSymbol(p, cancellationToken)) - .Where(p => p is not null) - .Select(ParameterInfo.Create!) - .ToEquatableArray(); - - // Hierarchy for determining lambda return type. - // - // 1. type conversion (not handled here) - // 2. explicit return type - // 3. implicit return type in expression body - // 4. implicit return type in block body - // 5. default void (or Task if async) - var (returnType, returnTypeSyntax) = lambdaExpression switch - { - // check for explicit return type - ParenthesizedLambdaExpressionSyntax { ReturnType: { } syntax } => ModelExtensions - .GetTypeInfo(sematicModel, syntax, cancellationToken) - .Type - is { } type - ? (type, syntax) - : (null, null), - - // Handle implicit return type for expression lambda - { Body: var expression and ExpressionSyntax } => ( - sematicModel.GetTypeInfo(expression, cancellationToken).Type, - null - ), - - // Handle implicit return type for block lambda - { Body: var block and BlockSyntax } => block - .DescendantNodes() - .OfType() - .FirstOrDefault(s => s.Expression is not null) - ?.Expression - is { } expr - ? (sematicModel.GetTypeInfo(expr, cancellationToken).Type, null) - : (null, null), - - // Default to void if no return type is found - _ => (null, null), - }; - - // get response type TypeInfo - TypeInfo? responseTypeInfo = returnType is not null - ? TypeInfo.Create(returnType, returnTypeSyntax) - : null; - - // determine if the lambda is async by checking kind - var isAsync = lambdaExpression.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword); - - // the full return type for use in function signatures. - var fullResponseType = ( - ReturnType: responseTypeInfo?.FullyQualifiedType, - IsAsync: isAsync - ) switch - { - (null, true) => TypeConstants.Task, - (null, false) => TypeConstants.Void, - (TypeConstants.Void, _) => TypeConstants.Void, - (TypeConstants.Task, _) => TypeConstants.Task, - (TypeConstants.ValueTask, _) => TypeConstants.ValueTask, - var (type, _) when type.StartsWith(TypeConstants.Task) => type, - var (type, _) when type.StartsWith(TypeConstants.ValueTask) => type, - (var type, true) => $"{TypeConstants.Task}<{type}>", - (_, _) => responseTypeInfo.Value.FullyQualifiedType, - }; - - var updatedResponseType = responseTypeInfo is { } info - ? info with - { - FullyQualifiedType = fullResponseType, - } - : TypeInfo.CreateFullyQualifiedType(fullResponseType); - - // determine if the delegate is returning awaitable value - var isAwaitable = - fullResponseType != TypeConstants.Void - && (isAsync || (returnType?.IsTypeAwaitable() ?? false)); - - return new DelegateInfo(parameters, isAwaitable, isAsync, updatedResponseType); - } - - private delegate DelegateInfo Updater( - DelegateInfo delegateInfo, - CancellationToken cancellationToken - ); -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs new file mode 100644 index 00000000..d1594d38 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/HandlerSyntaxProvider.cs @@ -0,0 +1,164 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator/StaticRouteHandlerModel/InvocationOperationExtensions.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; +using MinimalLambda.SourceGenerators.Models; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; + +namespace MinimalLambda.SourceGenerators; + +internal static class HandlerSyntaxProvider +{ + private static readonly string[] TargetMethodNames = ["MapHandler", "OnInit", "OnShutdown"]; + + internal static bool Predicate(SyntaxNode node, CancellationToken _) => + !node.IsGeneratedFile() + && node.TryGetMethodName(out var name) + && TargetMethodNames.Contains(name); + + internal static IMethodInfo? Transformer( + GeneratorSyntaxContext syntaxContext, + CancellationToken cancellationToken + ) + { + var context = new GeneratorContext(syntaxContext, cancellationToken); + + if (!TryGetInvocationOperation(context, out var targetOperation)) + return null; + + if (!targetOperation.TryGetHandlerMethod(context.SemanticModel, out var method)) + return null; + + return targetOperation.TargetMethod.Name switch + { + "MapHandler" => MapHandlerMethodInfo.Create(method, context), + "OnInit" => LifecycleMethodInfo.CreateForInit(method, context), + "OnShutdown" => LifecycleMethodInfo.CreateForShutdown(method, context), + var methodName => throw new InvalidOperationException($"Unknown method '{methodName}"), + }; + } + + private static bool TryGetInvocationOperation( + GeneratorContext context, + [NotNullWhen(true)] out IInvocationOperation? invocationOperation + ) + { + invocationOperation = null; + + var operation = context.SemanticModel.GetOperation(context.Node, context.CancellationToken); + + if ( + operation + is IInvocationOperation + { + TargetMethod.ContainingNamespace: + { + Name: "Builder", + ContainingNamespace: + { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, + }, + } targetOperation + && targetOperation.TargetMethod.ContainingAssembly.Name == "MinimalLambda" + && targetOperation.TryGetRouteHandlerArgument(out var routeHandlerParameter) + && routeHandlerParameter is { Parameter.Type: { } delegateType } + && SymbolEqualityComparer.Default.Equals( + delegateType, + context.WellKnownTypes.Get(WellKnownType.System_Delegate) + ) + ) + { + invocationOperation = targetOperation; + return true; + } + + return false; + } + + private static bool TryGetHandlerMethod( + this IInvocationOperation invocation, + SemanticModel semanticModel, + [NotNullWhen(true)] out IMethodSymbol? method + ) + { + method = null; + if (invocation.TryGetRouteHandlerArgument(out var argument)) + { + method = ResolveMethodFromOperation(argument, semanticModel); + return method is not null; + } + + return false; + } + + private static IMethodSymbol? ResolveMethodFromOperation( + IOperation operation, + SemanticModel semanticModel + ) => + operation switch + { + IArgumentOperation argument => ResolveMethodFromOperation( + argument.Value, + semanticModel + ), + IConversionOperation conv => ResolveMethodFromOperation(conv.Operand, semanticModel), + IDelegateCreationOperation del => ResolveMethodFromOperation(del.Target, semanticModel), + IFieldReferenceOperation { Field.IsReadOnly: true } f + when ResolveDeclarationOperation(f.Field, semanticModel) is { } op => + ResolveMethodFromOperation(op, semanticModel), + IAnonymousFunctionOperation anon => anon.Symbol, + ILocalFunctionOperation local => local.Symbol, + IMethodReferenceOperation method => method.Method, + IParenthesizedOperation parenthesized => ResolveMethodFromOperation( + parenthesized.Operand, + semanticModel + ), + _ => null, + }; + + private static bool TryGetRouteHandlerArgument( + this IInvocationOperation invocation, + [NotNullWhen(true)] out IArgumentOperation? argumentOperation + ) + { + argumentOperation = null; + var routeHandlerArgumentOrdinal = invocation.Arguments.Length - 1; + + foreach (var argument in invocation.Arguments) + if (argument.Parameter?.Ordinal == routeHandlerArgumentOrdinal) + { + argumentOperation = argument; + return true; + } + + return false; + } + + private static IOperation? ResolveDeclarationOperation( + ISymbol symbol, + SemanticModel? semanticModel + ) => + symbol + .DeclaringSyntaxReferences.Select(syntaxReference => syntaxReference.GetSyntax()) + .OfType() + .Where(syn => syn.Initializer?.Value is not null) + .Select(syn => + { + var expr = syn.Initializer!.Value; + var targetSemanticModel = semanticModel?.Compilation.GetSemanticModel( + expr.SyntaxTree + ); + return targetSemanticModel?.GetOperation(expr); + }) + .FirstOrDefault(operation => operation is not null); +} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs deleted file mode 100644 index 08b16bcd..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/LambdaApplicationBuilderBuildSyntaxProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class LambdaApplicationBuilderBuildSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken _) => - node.TryGetMethodName(out var name) && name == "Build"; - - internal static SimpleMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) - { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); - - if ( - operation - is IInvocationOperation - { - TargetMethod: - { - ContainingType.Name: "LambdaApplicationBuilder", - ContainingNamespace: - { - Name: "Builder", - ContainingNamespace: - { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true }, - }, - }, - } targetOperation - && targetOperation.TargetMethod.ContainingAssembly.Name == "MinimalLambda" - ) - { - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - (InvocationExpressionSyntax)targetOperation.Syntax, - cancellationToken - )!; - - return new SimpleMethodInfo( - targetOperation.TargetMethod.Name, - context.Node.CreateLocationInfo(), - InterceptableLocationInfo.CreateFrom(interceptableLocation) - ); - } - - return null; - } -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs deleted file mode 100644 index 152fbf81..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/MapHandlerSyntaxProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class MapHandlerSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.MapHandlerMethodName); - - internal static HigherOrderMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, _ => false, cancellationToken); -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs deleted file mode 100644 index c94bcced..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnInitSyntaxProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OnInitSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnInitMethodName); - - internal static HigherOrderMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); - - // we want to filter out the non-generic init method calls that use the method signature - // defined in ILambdaOnInitBuilder. this is LambdaInitDelegate. - // Func> - private static bool IsBaseOnShutdownCall(this DelegateInfo delegateInfo) => - delegateInfo - is { - ReturnTypeInfo.FullyQualifiedType: TypeConstants.TaskBool, - Parameters: [ - { TypeInfo.FullyQualifiedType: TypeConstants.ILambdaLifecycleContext }, - ], - }; -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs deleted file mode 100644 index 142643ea..00000000 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/OnShutdownSyntaxProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Threading; -using Microsoft.CodeAnalysis; -using MinimalLambda.SourceGenerators.Models; - -namespace MinimalLambda.SourceGenerators; - -internal static class OnShutdownSyntaxProvider -{ - internal static bool Predicate(SyntaxNode node, CancellationToken cancellationToken) => - HandlerInfoExtractor.Predicate(node, GeneratorConstants.OnShutdownMethodName); - - internal static HigherOrderMethodInfo? Transformer( - GeneratorSyntaxContext context, - CancellationToken cancellationToken - ) => HandlerInfoExtractor.Transformer(context, IsBaseOnShutdownCall, cancellationToken); - - // we want to filter out the non-generic shutdown method calls that use the method signature - // defined in ILambdaOnShutdownBuilder. this is LambdaShutdownDelegate. - // Func - private static bool IsBaseOnShutdownCall(this DelegateInfo delegateInfo) => - delegateInfo - is { - ReturnTypeInfo.FullyQualifiedType: TypeConstants.Task, - Parameters: [ - { TypeInfo.FullyQualifiedType: TypeConstants.ILambdaLifecycleContext }, - ], - }; -} diff --git a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs index a9a735ef..52281d89 100644 --- a/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs +++ b/src/MinimalLambda.SourceGenerators/SyntaxProviders/UseMiddlewareTSyntaxProvider.cs @@ -1,28 +1,44 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using MinimalLambda.SourceGenerators.Models; +using WellKnownType = MinimalLambda.SourceGenerators.WellKnownTypes.WellKnownTypeData.WellKnownType; namespace MinimalLambda.SourceGenerators; internal static class UseMiddlewareTSyntaxProvider { + private const string TargetMethodName = "UseMiddleware"; + internal static bool Predicate(SyntaxNode node, CancellationToken _) => - !node.IsGeneratedFile() && node.TryGetMethodName(out var name) && name == "UseMiddleware"; + !node.IsGeneratedFile() && node.TryGetMethodName(out var name) && name == TargetMethodName; internal static UseMiddlewareTInfo? Transformer( - GeneratorSyntaxContext context, + GeneratorSyntaxContext syntaxContext, CancellationToken cancellationToken ) { - var operation = context.SemanticModel.GetOperation(context.Node, cancellationToken); + var context = new GeneratorContext(syntaxContext, cancellationToken); + + return TryGetInvocationOperation(context, out var targetOperation) + ? UseMiddlewareTInfo.Create(targetOperation, context) + : null; + } + + private static bool TryGetInvocationOperation( + GeneratorContext context, + [NotNullWhen(true)] out IInvocationOperation? invocationOperation + ) + { + invocationOperation = null; + + var operation = context.SemanticModel.GetOperation(context.Node, context.CancellationToken); if ( operation - is not IInvocationOperation + is IInvocationOperation { TargetMethod: { @@ -36,46 +52,17 @@ is not IInvocationOperation }, }, } targetOperation - || !targetOperation - .TargetMethod.ConstructedFrom.TypeParameters[0] - .ConstraintTypes.Any(c => - c.Name == "ILambdaMiddleware" - && c.ContainingNamespace - is { Name: "MinimalLambda", ContainingNamespace.IsGlobalNamespace: true } - ) - ) - return null; - - // get class TypeInfo - var middlewareClassType = targetOperation.TargetMethod.TypeArguments[0]; - - // Get location of the generic argument - Location? genericArgumentLocation = null; - if ( - targetOperation.Syntax is InvocationExpressionSyntax - { - Expression: MemberAccessExpressionSyntax { Name: GenericNameSyntax genericName }, - } + && targetOperation.TargetMethod.ConstructedFrom.TypeParameters.FirstOrDefault() + is { } typeParameter + && typeParameter.ConstraintTypes.Any(c => + context.WellKnownTypes.IsType(c, WellKnownType.MinimalLambda_ILambdaMiddleware) + ) ) { - // Get the first type argument's location - var typeArgument = genericName.TypeArgumentList.Arguments[0]; - genericArgumentLocation = typeArgument.GetLocation(); + invocationOperation = targetOperation; + return true; } - var classInfo = ClassInfo.Create(middlewareClassType); - - var interceptableLocation = context.SemanticModel.GetInterceptableLocation( - (InvocationExpressionSyntax)targetOperation.Syntax, - cancellationToken - )!; - - var useMiddlewareTInfo = new UseMiddlewareTInfo( - InterceptableLocationInfo.CreateFrom(interceptableLocation), - classInfo, - genericArgumentLocation?.CreateLocationInfo() - ); - - return useMiddlewareTInfo; + return false; } } diff --git a/src/MinimalLambda.SourceGenerators/TemplateHelper.cs b/src/MinimalLambda.SourceGenerators/TemplateHelper.cs deleted file mode 100644 index 0cb4a1a7..00000000 --- a/src/MinimalLambda.SourceGenerators/TemplateHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using Scriban; - -namespace MinimalLambda.SourceGenerators; - -internal static class TemplateHelper -{ - /// Loads a Scriban template from embedded resources - /// Name of the template file (without .scriban extension) - /// Parsed Scriban template - internal static Template LoadTemplate(string relativePath) - { - var baseName = Assembly.GetExecutingAssembly().GetName().Name; - var templateName = relativePath - .TrimStart('.') - .Replace(Path.DirectorySeparatorChar, '.') - .Replace(Path.AltDirectorySeparatorChar, '.'); - - var manifestTemplateName = Assembly - .GetExecutingAssembly() - .GetManifestResourceNames() - .FirstOrDefault(x => x!.EndsWith(templateName, StringComparison.InvariantCulture)); - - if (string.IsNullOrEmpty(manifestTemplateName)) - throw new InvalidOperationException( - $"Did not find required resource ending in '{templateName}' in assembly '{baseName}'." - ); - - using var stream = Assembly - .GetExecutingAssembly() - .GetManifestResourceStream(manifestTemplateName); - if (stream == null) - throw new FileNotFoundException( - $"Template '{relativePath}' not found in embedded resources." - ); - - using var reader = new StreamReader(stream); - var templateContent = reader.ReadToEnd(); - - var template = Template.Parse(templateContent); - if (template.HasErrors) - { - var errors = string.Join("; ", template.Messages.Select(m => m.ToString())); - throw new InvalidOperationException( - $"Template parsing errors in '{templateName}': {errors}" - ); - } - - return template; - } -} diff --git a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban index c0ac89ce..b63b9555 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/GenericHandler.scriban @@ -1,43 +1,36 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class GeneratedLambda{{ name }}BuilderExtensions { {{~ for call in calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] - internal static {{ call.target_type }} {{ name }}Interceptor{{ for.index }}{{ call.generic_parameters }}( - this {{ call.target_type }} application, + {{ call.interceptable_location_attribute }} + internal static ILambda{{ name }}Builder {{ name }}Interceptor{{ for.index }}( + this ILambda{{ name }}Builder application, Delegate handler ) { - var castHandler = {{ call.handler_signature }}; + var castHandler = Utilities.Cast(handler, {{ call.delegate_cast_type }}); return application.{{ name }}({{ name }}); - {{ if call.should_await ~}} async {{ end ~}}{{ call.wrapper_return_type }} {{ name }}(ILambdaLifecycleContext context) + {{ if call.should_await ~}} async {{ end ~}}{{ call.return_type }} {{ name }}(ILambdaLifecycleContext context) { - {{~ if call.has_any_keyed_service_parameter ~}} + {{~ if call.has_any_from_keyed_services ~}} if (context.ServiceProvider.GetService() is not IServiceProviderIsKeyedService) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } {{~ end ~}} - {{~ for handler_arg in call.handler_args ~}} - // {{ handler_arg.string }} + {{~ for handler_arg in call.parameter_assignments ~}} var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} - {{ if call.should_return_response ~}} var response = {{ end }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.handler_args }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); - {{~ if call.should_return_response ~}} - {{~ if call.should_wrap_response ~}} - return Task.FromResult(response); - {{~ else ~}} - return response; - {{~ end ~}} - {{~ else if call.default_return_value != null ~}} - return {{ call.default_return_value }}; + {{ call.handle_response_assignment }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{~ if call.handle_returning_from_method != "" ~}} + {{ call.handle_returning_from_method }} {{~ end ~}} } } {{~ if !for.last ~}} - + {{~ end ~}} {{~ end ~}} } \ No newline at end of file diff --git a/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban b/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban index 37a13190..b741545d 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/InterceptsLocationAttribute.scriban @@ -15,7 +15,7 @@ namespace System.Runtime.CompilerServices { using System.CodeDom.Compiler; - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] file sealed class InterceptsLocationAttribute : Attribute { diff --git a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban index 65412123..0c80b3bf 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/MapHandler.scriban @@ -1,61 +1,60 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class GeneratedLambdaInvocationBuilderExtensions { private const string EventFeatureProviderKey = "__EventFeatureProvider"; private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; {{~ for call in map_handler_calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] + {{ call.interceptable_location_attribute }} internal static ILambdaInvocationBuilder MapHandlerInterceptor{{ for.index }}( this ILambdaInvocationBuilder application, Delegate handler ) { - var castHandler = {{ call.handler_signature }}; + var castHandler = Utilities.Cast(handler, {{ call.delegate_cast_type }}); application.Handle(InvocationDelegate); - {{~ if call.is_event_feature_required ~}} + {{~ if call.has_event && !call.is_event_type_stream ~}} if (!application.Properties.ContainsKey(EventFeatureProviderKey)) application.Properties[EventFeatureProviderKey] = application .Services.GetRequiredService() - .Create<{{ call.input_event.type }}>(); + .Create<{{ call.event_type }}>(); {{~ end ~}} - {{~ if call.is_response_feature_required ~}} + {{~ if call.has_response && !call.is_response_type_stream ~}} if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create<{{ call.output_response.response_type }}>(); + .Create<{{ call.unwrapped_response_type }}>(); {{~ end ~}} return application; - {{ if call.should_await ~}} async {{ end ~}}Task InvocationDelegate(ILambdaInvocationContext context) + {{ if call.is_awaitable ~}} async {{ end ~}}Task InvocationDelegate(ILambdaInvocationContext context) { - {{~ if call.has_any_keyed_service_parameter ~}} + {{~ if call.has_any_from_keyed_services ~}} if (context.ServiceProvider.GetService() is not IServiceProviderIsKeyedService) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } {{~ end ~}} - {{~ for handler_arg in call.handler_args ~}} - // {{ handler_arg.string }} + {{~ for handler_arg in call.parameter_assignments ~}} var arg{{ for.index }} = {{ handler_arg.assignment }}; {{~ end ~}} - {{ if call.output_response != null; ~}} var response = {{ end }}{{ if call.should_await ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.handler_args }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); - {{~ if call.output_response != null; ~}} - {{~ if call.output_response.response_is_stream ~}} + {{ if call.has_response ~}} var response = {{ end }}{{ if call.is_awaitable ~}} await {{ end ~}} castHandler.Invoke({{ for arg in call.parameter_assignments }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + {{~ if call.has_response ~}} + {{~ if call.is_response_type_stream ~}} context.Features.GetRequired().ResponseStream = response; {{~ else ~}} - if (context.Features.Get() is not IResponseFeature<{{ call.output_response.response_type }}> responseFeature) + if (context.Features.Get() is not IResponseFeature<{{ call.unwrapped_response_type }}> responseFeature) { - throw new InvalidOperationException($"Response feature for type '{{ call.output_response.response_type }}' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type '{{ call.unwrapped_response_type }}' is not available in the collection."); } responseFeature.SetResponse(response); {{~ end ~}} {{~ end ~}} - {{~ if !call.should_await ~}} + {{~ if !call.is_awaitable ~}} return Task.CompletedTask; {{~ end ~}} } diff --git a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban index 944a7467..6ce077fd 100644 --- a/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban +++ b/src/MinimalLambda.SourceGenerators/Templates/UseMiddlewareT.scriban @@ -1,17 +1,17 @@ - {{ generated_code_attribute }} + {{ generated_code_attribute.value }} file static class UseMiddlewareExtensions { {{~ for call in calls ~}} - [InterceptsLocation({{ call.location.version }}, "{{ call.location.data }}")] + {{ call.interceptable_location_attribute }} internal static ILambdaInvocationBuilder UseMiddleware{{ for.index }}( this ILambdaInvocationBuilder builder, params object[] args ) where T : ILambdaMiddleware { - var resolver = new {{ call.short_middleware_class_name }}Resolver{{ for.index }}(args); - - {{~ if call.is_async_disposable ~}} + var resolver = new {{ call.class_info.short_name }}Resolver{{ for.index }}(args); + + {{~ if call.class_info.implements_async_disposable ~}} builder.Use(next => { return async context => @@ -20,7 +20,7 @@ await middleware.InvokeAsync(context, next); }; }); - {{~ else if call.is_disposable ~}} + {{~ else if call.class_info.implements_disposable ~}} builder.Use(next => { return async context => @@ -42,57 +42,56 @@ return builder; } - private class {{ call.short_middleware_class_name }}Resolver{{ for.index }} + private class {{ call.class_info.short_name }}Resolver{{ for.index }} { - {{~ if call.any_parameters && !call.all_from_services ~}} + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} private const int NotCached = -1; private const int FromServices = -2; private bool _cacheBuilt = false; {{~ end ~}} private readonly object[] _args; - {{~ for parameter in call.parameters ~}} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} - private int _cache{{ for.index }} = NotCached; // {{ parameter.fully_qualified_type }} + private int _cache{{ for.index }} = NotCached; // {{ parameter.globally_qualified_type }} {{~ end ~}} {{~ end ~}} - {{~ if call.any_parameters && !call.all_from_services ~}} - + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} + {{~ end ~}} - internal {{ call.short_middleware_class_name }}Resolver{{ for.index }}(object[] args) => _args = args; + internal {{ call.class_info.short_name }}Resolver{{ for.index }}(object[] args) => _args = args; - internal {{ call.full_middleware_class_name }} Create(ILambdaInvocationContext context) + internal {{ call.class_info.globally_qualified_name }} Create(ILambdaInvocationContext context) { - {{~ if call.any_parameters ~}} - {{~ if !call.all_from_services ~}} + {{~ if call.class_info.parameter_infos.count > 0 ~}} + {{~ if !call.class_info.all_parameters_from_services ~}} if (!_cacheBuilt) BuildResolutionCache(); {{~ end ~}} - {{~ for parameter in call.parameters ~}} - // {{ parameter.string }} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if parameter.from_services ~}} - var arg{{ for.index }} = {{ parameter.assignment }}; + var arg{{ for.index }} = {{ parameter.from_services_assignment }}; {{~ else ~}} var arg{{ for.index }} = _cache{{ for.index }} >= 0 - ? ({{ parameter.fully_qualified_type }})_args[_cache{{ for.index }}] + ? ({{ parameter.globally_qualified_type }})_args[_cache{{ for.index }}] {{~ if parameter.from_arguments ~}} - : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type '{{ parameter.fully_qualified_type }}' must be provided in args"); + : throw new InvalidOperationException("Parameter '{{ parameter.name }}' of type '{{ parameter.globally_qualified_type }}' must be provided in args"); {{~ else ~}} - : {{ parameter.assignment }}; + : {{ parameter.from_services_assignment }}; {{~ end ~}} {{~ end ~}} - + {{~ end ~}} {{~ end ~}} - return new {{ call.full_middleware_class_name }}({{ for arg in call.parameters }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); + return new {{ call.class_info.globally_qualified_name }}({{ for arg in call.class_info.parameter_infos }}arg{{ for.index }}{{ if !for.last }}, {{ end }}{{ end }}); } - {{~ if call.any_parameters && !call.all_from_services ~}} - + {{~ if call.class_info.parameter_infos.count > 0 && !call.class_info.all_parameters_from_services ~}} + private void BuildResolutionCache() { - {{~ for parameter in call.parameters ~}} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} _cache{{ for.index }} = FromServices; {{~ end ~}} @@ -106,9 +105,9 @@ switch (arg) { - {{~ for parameter in call.parameters ~}} + {{~ for parameter in call.class_info.parameter_infos ~}} {{~ if !parameter.from_services ~}} - case {{ parameter.fully_qualified_type_not_null }} when _cache{{ for.index }} == FromServices: + case {{ parameter.globally_qualified_not_nullable_type }} when _cache{{ for.index }} == FromServices: _cache{{ for.index }} = i; break; {{~ end ~}} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs new file mode 100644 index 00000000..b41c0a0d --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/BoundedCacheWithFactory.cs @@ -0,0 +1,83 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +using System; +using System.Collections.Generic; + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +// This type is copied from +// https://github.com/dotnet/roslyn-analyzers/blob/9b58ec3ad33353d1a523cda8c4be38eaefc80ad8/src/Utilities/Compiler/BoundedCacheWithFactory.cs + +/// +/// Provides bounded cache for analyzers. Acts as a good alternative to +/// when the +/// cached value has a cyclic reference to the key preventing early garbage collection of entries. +/// +internal class BoundedCacheWithFactory + where TKey : class +{ + // Bounded weak reference cache. + // Size 5 is an arbitrarily chosen bound, which can be tuned in future as required. + private readonly List> _weakReferencedEntries = new() + { + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + new WeakReference(null), + }; + + public TValue GetOrCreateValue(TKey key, Func valueFactory) + { + lock (_weakReferencedEntries) + { + var indexToSetTarget = -1; + for (var i = 0; i < _weakReferencedEntries.Count; i++) + { + var weakReferencedEntry = _weakReferencedEntries[i]; + if (!weakReferencedEntry.TryGetTarget(out var cachedEntry) || cachedEntry == null) + { + if (indexToSetTarget == -1) + indexToSetTarget = i; + + continue; + } + + if (Equals(cachedEntry.Key, key)) + { + // Move the cache hit item to the end of the list + // so it would be least likely to be evicted on next cache miss. + _weakReferencedEntries.RemoveAt(i); + _weakReferencedEntries.Add(weakReferencedEntry); + return cachedEntry.Value; + } + } + + if (indexToSetTarget == -1) + indexToSetTarget = 0; + + var newEntry = new Entry(key, valueFactory(key)); + _weakReferencedEntries[indexToSetTarget].SetTarget(newEntry); + return newEntry.Value; + } + } + + private sealed class Entry + { + public Entry(TKey key, TValue value) + { + Key = key; + Value = value; + } + + public TKey Key { get; } + + public TValue Value { get; } + } +} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs new file mode 100644 index 00000000..f26050e4 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypeData.cs @@ -0,0 +1,108 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +// ReSharper disable InconsistentNaming + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +internal static class WellKnownTypeData +{ + public enum WellKnownType + { + Microsoft_Extensions_Primitives_StringValues, + System_Threading_CancellationToken, + System_Security_Claims_ClaimsPrincipal, + System_DateOnly, + System_DateTimeOffset, + System_IO_Stream, + System_IO_Pipelines_PipeReader, + System_IFormatProvider, + System_Uri, + System_String, + System_Guid, + System_TimeSpan, + Microsoft_Extensions_Hosting_GenericHostWebHostBuilderExtensions, + Microsoft_Extensions_Hosting_HostingHostBuilderExtensions, + System_Delegate, + System_Threading_Tasks_Task, + System_Threading_Tasks_Task_T, + System_Threading_Tasks_ValueTask, + System_Threading_Tasks_ValueTask_T, + System_Reflection_ParameterInfo, + System_IParsable_T, + Microsoft_Extensions_DependencyInjection_OutputCacheConventionBuilderExtensions, + Microsoft_Extensions_DependencyInjection_PolicyServiceCollectionExtensions, + Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute, + Microsoft_Extensions_DependencyInjection_IServiceCollection, + System_AttributeUsageAttribute, + System_Collections_Generic_Dictionary_2, + Amazon_Lambda_Core_ILambdaContext, + System_Action, + System_Func, + System_IAsyncDisposable, + System_IDisposable, + System_IServiceProvider, + System_Void, + MinimalLambda_ILambdaInvocationContext, + MinimalLambda_ILambdaLifecycleContext, + MinimalLambda_Builder_EventAttribute, + MinimalLambda_Builder_FromArgumentsAttribute, + MinimalLambda_Builder_FromEventAttribute, + MinimalLambda_Builder_FromServicesAttribute, + MinimalLambda_Builder_MiddlewareConstructorAttribute, + System_Boolean, + MinimalLambda_ILambdaMiddleware, + } + + public static readonly string[] WellKnownTypeNames = + [ + "Microsoft.Extensions.Primitives.StringValues", + "System.Threading.CancellationToken", + "System.Security.Claims.ClaimsPrincipal", + "System.DateOnly", + "System.DateTimeOffset", + "System.IO.Stream", + "System.IO.Pipelines.PipeReader", + "System.IFormatProvider", + "System.Uri", + "System.String", + "System.Guid", + "System.TimeSpan", + "Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions", + "Microsoft.Extensions.Hosting.HostingHostBuilderExtensions", + "System.Delegate", + "System.Threading.Tasks.Task", + "System.Threading.Tasks.Task`1", + "System.Threading.Tasks.ValueTask", + "System.Threading.Tasks.ValueTask`1", + "System.Reflection.ParameterInfo", + "System.IParsable`1", + "Microsoft.Extensions.DependencyInjection.OutputCacheConventionBuilderExtensions", + "Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions", + "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute", + "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "System.AttributeUsageAttribute", + "System.Collections.Generic.Dictionary`2", + "Amazon.Lambda.Core.ILambdaContext", + "System.Action", + "System.Func", + "System.IAsyncDisposable", + "System.IDisposable", + "System.IServiceProvider", + "System.Void", + "MinimalLambda.ILambdaInvocationContext", + "MinimalLambda.ILambdaLifecycleContext", + "MinimalLambda.Builder.EventAttribute", + "MinimalLambda.Builder.FromArgumentsAttribute", + "MinimalLambda.Builder.FromEventAttribute", + "MinimalLambda.Builder.FromServicesAttribute", + "MinimalLambda.Builder.MiddlewareConstructorAttribute", + "System.Boolean", + "MinimalLambda.ILambdaMiddleware", + ]; +} diff --git a/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs new file mode 100644 index 00000000..bdda23b7 --- /dev/null +++ b/src/MinimalLambda.SourceGenerators/WellKnownTypes/WellKnownTypes.cs @@ -0,0 +1,166 @@ +// Portions of this file are derived from aspnetcore +// Source: +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/src/Mvc/Mvc.Testing/src/DeferredHostBuilder.cs +// Copyright (c) .NET Foundation and Contributors +// Licensed under the MIT License +// See THIRD-PARTY-LICENSES.txt file in the project root or visit +// https://github.com/dotnet/aspnetcore/blob/v10.0.0/LICENSE.txt + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis; + +namespace MinimalLambda.SourceGenerators.WellKnownTypes; + +internal class WellKnownTypes +{ + private static readonly BoundedCacheWithFactory< + Compilation, + WellKnownTypes + > LazyWellKnownTypesCache = new(); + + public static WellKnownTypes GetOrCreate(Compilation compilation) => + LazyWellKnownTypesCache.GetOrCreateValue(compilation, static c => new WellKnownTypes(c)); + + private readonly INamedTypeSymbol?[] _lazyWellKnownTypes; + private readonly Compilation _compilation; + + static WellKnownTypes() => AssertEnumAndTableInSync(); + + [Conditional("DEBUG")] + private static void AssertEnumAndTableInSync() + { + for (var i = 0; i < WellKnownTypeData.WellKnownTypeNames.Length; i++) + { + var name = WellKnownTypeData.WellKnownTypeNames[i]; + var typeId = (WellKnownTypeData.WellKnownType)i; + + var typeIdName = typeId.ToString().Replace("__", "+").Replace('_', '.'); + + var separator = name.IndexOf('`'); + if (separator >= 0) + { + // Ignore type parameter qualifier for generic types. + name = name.Substring(0, separator); + typeIdName = typeIdName.Substring(0, separator); + } + + Debug.Assert( + name == typeIdName, + $"Enum name ({typeIdName}) and type name ({name}) must match at {i}" + ); + } + } + + private WellKnownTypes(Compilation compilation) + { + _lazyWellKnownTypes = new INamedTypeSymbol?[WellKnownTypeData.WellKnownTypeNames.Length]; + _compilation = compilation; + } + + public INamedTypeSymbol Get(SpecialType type) => _compilation.GetSpecialType(type); + + public INamedTypeSymbol Get(WellKnownTypeData.WellKnownType type) + { + var index = (int)type; + var symbol = _lazyWellKnownTypes[index]; + if (symbol is not null) + return symbol; + + // Symbol hasn't been added to the cache yet. + // Resolve symbol from name, cache, and return. + return GetAndCache(index); + } + + private INamedTypeSymbol GetAndCache(int index) + { + var result = GetTypeByMetadataNameInTargetAssembly( + WellKnownTypeData.WellKnownTypeNames[index] + ); + if (result == null) + throw new InvalidOperationException( + $"Failed to resolve well-known type '{WellKnownTypeData.WellKnownTypeNames[index]}'." + ); + + Interlocked.CompareExchange(ref _lazyWellKnownTypes[index], result, null); + + // GetTypeByMetadataName should always return the same instance for a name. + // To ensure we have a consistent value, for thread safety, return symbol set in the array. + return _lazyWellKnownTypes[index]!; + } + + // Filter for types within well-known (framework-owned) assemblies only. + private INamedTypeSymbol? GetTypeByMetadataNameInTargetAssembly(string metadataName) + { + var types = _compilation.GetTypesByMetadataName(metadataName); + if (types.Length == 0) + return null; + + if (types.Length == 1) + return types[0]; + + // Multiple types match the name. This is most likely caused by someone reusing the + // namespace + type name in their apps or libraries. + // Workaround this situation by prioritizing types in System and Microsoft assemblies. + foreach (var type in types) + if ( + type.ContainingAssembly.Identity.Name.StartsWith( + "System.", + StringComparison.Ordinal + ) + || type.ContainingAssembly.Identity.Name.StartsWith( + "Microsoft.", + StringComparison.Ordinal + ) + ) + return type; + + return null; + } + + public bool IsType(ITypeSymbol type, params WellKnownTypeData.WellKnownType[] wellKnownTypes) => + IsType(type, wellKnownTypes, out _); + + public bool IsType( + ITypeSymbol type, + WellKnownTypeData.WellKnownType[] wellKnownTypes, + [NotNullWhen(true)] out WellKnownTypeData.WellKnownType? match + ) + { + foreach (var wellKnownType in wellKnownTypes) + if (SymbolEqualityComparer.Default.Equals(type, Get(wellKnownType))) + { + match = wellKnownType; + return true; + } + + match = null; + return false; + } + + public bool Implements( + ITypeSymbol type, + WellKnownTypeData.WellKnownType[] interfaceWellKnownTypes + ) + { + foreach (var wellKnownType in interfaceWellKnownTypes) + if (Implements(type, Get(wellKnownType))) + return true; + + return false; + } + + public static bool Implements(ITypeSymbol? type, ITypeSymbol interfaceType) + { + if (type is null) + return false; + + foreach (var t in type.AllInterfaces) + if (SymbolEqualityComparer.Default.Equals(t, interfaceType)) + return true; + + return false; + } +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs index 90754699..ce61ba28 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/DiagnosticTests.cs @@ -72,7 +72,7 @@ public void Test_MultipleParametersWithRequestAttribute() """ ); - diagnostics.Length.Should().Be(2); + diagnostics.Length.Should().Be(1); foreach (var diagnostic in diagnostics) { diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs index da67c24e..c709cb4f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/GeneratorTestHelpers.cs @@ -24,7 +24,17 @@ internal static Task Verify(string source, int expectedTrees = 1) var result = driver.GetRunResult(); - // result.Diagnostics.Length.Should().Be(0);s + result + .Diagnostics.Should() + .BeEmpty( + "code should be generated without errors, but found:\n" + + string.Join( + "\n---\n", + result.Diagnostics.Select(e => + $" - {e.Id}: {e.GetMessage()} at {e.Location}" + ) + ) + ); // Reparse generated trees with the same parse options as the original compilation // to ensure consistent syntax tree features (e.g., InterceptorsNamespaces) @@ -135,7 +145,7 @@ .. Net80.References.All.ToList(), compilationOptions ); - var generator = new MapHandlerIncrementalGenerator().AsSourceGenerator(); + var generator = new MinimalLambdaGenerator().AsSourceGenerator(); var driver = CSharpGeneratorDriver.Create(generator); var updatedDriver = driver.RunGenerators(compilation, CancellationToken.None); diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs index 79505b21..5832736c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_AllInputSources#LambdaHandler.g.verified.cs @@ -65,19 +65,12 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = string, Name = request, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = cancellationToken, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg2 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service0, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key0", Type = string, BaseType = object } } var arg3 = context.ServiceProvider.GetRequiredKeyedService("key0"); - // ParameterInfo { Type = global::IService?, Name = service1, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg4 = context.ServiceProvider.GetKeyedService("key1"); - // ParameterInfo { Type = global::IService, Name = service2, Source = Service, IsNullable = False, IsOptional = False} var arg5 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service3, Source = Service, IsNullable = True, IsOptional = False} var arg6 = context.ServiceProvider.GetService(); castHandler.Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6); return Task.CompletedTask; @@ -89,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs index 3d92da48..2b807f81 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoReturn_TypeCast#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -75,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs index 35ffe2a2..34fa03ba 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_NoTypeInfo_TypeCast#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = string, Name = input, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs index d0c4a47a..ab237dd5 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnExplicitType#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs index d8373096..6a1fa381 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -48,7 +48,7 @@ internal static ILambdaInvocationBuilder MapHandlerInterceptor0( Delegate handler ) { - var castHandler = Utilities.Cast(handler, string? (string arg0, global::IService arg1) => throw null!); + var castHandler = Utilities.Cast(handler, string (string arg0, global::IService arg1) => throw null!); application.Handle(InvocationDelegate); @@ -60,20 +60,18 @@ Delegate handler if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create(); + .Create(); return application; Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); - if (context.Features.Get() is not IResponseFeature responseFeature) + if (context.Features.Get() is not IResponseFeature responseFeature) { - throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type 'string' is not available in the collection."); } responseFeature.SetResponse(response); return Task.CompletedTask; diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs index 345ce2b8..eeb4e588 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_ReturnString#LambdaHandler.g.verified.cs @@ -66,7 +66,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -83,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs index 9f2d746a..3974a582 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast#LambdaHandler.g.verified.cs @@ -61,7 +61,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -78,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs index 4b084524..09b43da1 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/BlockLambdaVerifyTests.Test_BlockLambda_TypeCast_InputFromKeyedServices#LambdaHandler.g.verified.cs @@ -65,7 +65,6 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("key"); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -82,4 +81,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs index a1dceb41..acda23b0 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationToken#LambdaHandler.g.verified.cs @@ -61,7 +61,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = cancellationToken, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -78,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs index 7e4a171d..14d978a6 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaContext#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs index 070aa8ab..4dca0f3f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaHostContext#LambdaHandler.g.verified.cs @@ -62,9 +62,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::MinimalLambda.ILambdaInvocationContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -81,4 +79,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs index e1cbf526..f3414fef 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_AsksForCancellationTokenAndLambdaInvocationContext#LambdaHandler.g.verified.cs @@ -61,9 +61,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = ct, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::MinimalLambda.ILambdaInvocationContext, Name = ctx, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -80,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs index 37960d92..37d1b8e9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ComplexInput_ComplexOutput#LambdaHandler.g.verified.cs @@ -66,11 +66,8 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::CustomRequest, Name = request, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg2 = context; var response = await castHandler.Invoke(arg0, arg1, arg2); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -86,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs index 241f6d2a..488b51e2 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_Async#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs index 241f6d2a..488b51e2 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs index ce029764..00bbfe8c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputDi_AsyncAndAwait_EventAndResponseDifferentNamespace#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler async Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::MyNamespace.Event, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = await castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -84,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs index e2bb3a78..94c64b0d 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_InputStream#LambdaHandler.g.verified.cs @@ -56,7 +56,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::System.IO.Stream, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.Features.GetRequired().EventStream; castHandler.Invoke(arg0); return Task.CompletedTask; @@ -68,4 +67,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_MainOverload_NoOp#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs index e8a71a8d..6c9452a0 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnExplicitNullable#LambdaHandler.g.verified.cs @@ -66,9 +66,7 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = int?, Name = input, Source = Event, IsNullable = True, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -85,4 +83,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs index e7472deb..f6e58e3c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnImplicitNullable#LambdaHandler.g.verified.cs @@ -48,7 +48,7 @@ internal static ILambdaInvocationBuilder MapHandlerInterceptor0( Delegate handler ) { - var castHandler = Utilities.Cast(handler, string? (string? arg0, global::IService arg1) => throw null!); + var castHandler = Utilities.Cast(handler, string (string? arg0, global::IService arg1) => throw null!); application.Handle(InvocationDelegate); @@ -60,20 +60,18 @@ Delegate handler if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) application.Properties[ResponseFeatureProviderKey] = application. Services.GetRequiredService() - .Create(); + .Create(); return application; Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string?, Name = input, Source = Event, IsNullable = True, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); - if (context.Features.Get() is not IResponseFeature responseFeature) + if (context.Features.Get() is not IResponseFeature responseFeature) { - throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + throw new InvalidOperationException($"Response feature for type 'string' is not available in the collection."); } responseFeature.SetResponse(response); return Task.CompletedTask; diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs new file mode 100644 index 00000000..e1ef8a9a --- /dev/null +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_NullableInput_ReturnNullableValueType#LambdaHandler.g.verified.cs @@ -0,0 +1,86 @@ +//HintName: LambdaHandler.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +#nullable enable + +namespace System.Runtime.CompilerServices +{ + using System.CodeDom.Compiler; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace MinimalLambda.Generated +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using MinimalLambda; + using MinimalLambda.Builder; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + file static class GeneratedLambdaInvocationBuilderExtensions + { + private const string EventFeatureProviderKey = "__EventFeatureProvider"; + private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; + + [InterceptsLocation(1, "REPLACED")] + internal static ILambdaInvocationBuilder MapHandlerInterceptor0( + this ILambdaInvocationBuilder application, + Delegate handler + ) + { + var castHandler = Utilities.Cast(handler, global::MyStruct? (int? arg0, global::IService arg1) => throw null!); + + application.Handle(InvocationDelegate); + + if (!application.Properties.ContainsKey(EventFeatureProviderKey)) + application.Properties[EventFeatureProviderKey] = application + .Services.GetRequiredService() + .Create(); + + if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) + application.Properties[ResponseFeatureProviderKey] = application. + Services.GetRequiredService() + .Create(); + + return application; + + Task InvocationDelegate(ILambdaInvocationContext context) + { + var arg0 = context.GetRequiredEvent(); + var arg1 = context.ServiceProvider.GetRequiredService(); + var response = castHandler.Invoke(arg0, arg1); + if (context.Features.Get() is not IResponseFeature responseFeature) + { + throw new InvalidOperationException($"Response feature for type 'global::MyStruct?' is not available in the collection."); + } + responseFeature.SetResponse(response); + return Task.CompletedTask; + } + } + } + + file static class Utilities + { + internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; + } +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs new file mode 100644 index 00000000..342f3aea --- /dev/null +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_OptionalInjectedParam#LambdaHandler.g.verified.cs @@ -0,0 +1,80 @@ +//HintName: LambdaHandler.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + +#nullable enable + +namespace System.Runtime.CompilerServices +{ + using System.CodeDom.Compiler; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace MinimalLambda.Generated +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using MinimalLambda; + using MinimalLambda.Builder; + + [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] + file static class GeneratedLambdaInvocationBuilderExtensions + { + private const string EventFeatureProviderKey = "__EventFeatureProvider"; + private const string ResponseFeatureProviderKey = "__ResponseFeatureProvider"; + + [InterceptsLocation(1, "REPLACED")] + internal static ILambdaInvocationBuilder MapHandlerInterceptor0( + this ILambdaInvocationBuilder application, + Delegate handler + ) + { + var castHandler = Utilities.Cast(handler, string? (global::IService? arg0 = default) => throw null!); + + application.Handle(InvocationDelegate); + + if (!application.Properties.ContainsKey(ResponseFeatureProviderKey)) + application.Properties[ResponseFeatureProviderKey] = application. + Services.GetRequiredService() + .Create(); + + return application; + + Task InvocationDelegate(ILambdaInvocationContext context) + { + var arg0 = context.ServiceProvider.GetService(); + var response = castHandler.Invoke(arg0); + if (context.Features.Get() is not IResponseFeature responseFeature) + { + throw new InvalidOperationException($"Response feature for type 'string?' is not available in the collection."); + } + responseFeature.SetResponse(response); + return Task.CompletedTask; + } + } + } + + file static class Utilities + { + internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; + } +} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs index bb7d973e..b376fae7 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/ExpressionLambdaVerifyTests.Test_ExpressionLambda_ReturnExplicitType#LambdaHandler.g.verified.cs @@ -66,7 +66,6 @@ Delegate handler Task InvocationDelegate(ILambdaInvocationContext context) { - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); var response = castHandler.Invoke(arg0); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -83,4 +82,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs index f906373c..cf1e35e3 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_FloatingPointTypes#LambdaHandler.g.verified.cs @@ -60,9 +60,7 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (double)3.14, Type = double, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((double)3.14); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (float)3.14, Type = float, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((float)3.14); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -74,4 +72,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs index 44d8b19d..5e9a2a45 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_IntAndLongKeys#LambdaHandler.g.verified.cs @@ -60,9 +60,7 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (int)42, Type = int, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((int)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (long)42, Type = long, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((long)42); castHandler.Invoke(arg0, arg1); return Task.CompletedTask; @@ -74,4 +72,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs index d212a087..7065b4c9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_OtherTypes#LambdaHandler.g.verified.cs @@ -60,13 +60,9 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = true, Type = bool, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService(true); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = 'A', Type = char, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService('A'); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = typeof(global::Service), Type = global::System.Type, BaseType = global::System.Reflection.MemberInfo } } var arg2 = context.ServiceProvider.GetRequiredKeyedService(typeof(global::Service)); - // ParameterInfo { Type = global::IService, Name = serviceD, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = null, Type = object, BaseType = } } var arg3 = context.ServiceProvider.GetRequiredKeyedService(null); castHandler.Invoke(arg0, arg1, arg2, arg3); return Task.CompletedTask; @@ -78,4 +74,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs index 21d6c492..2e581b92 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_SmallIntegerTypes#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (short)42, Type = short, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((short)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (byte)42, Type = byte, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((byte)42); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (sbyte)42, Type = sbyte, BaseType = global::System.ValueType } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((sbyte)42); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs index 18a0af9b..54aa5c8b 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_StringAndEnumKeys#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("myKey"); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "my\nKey", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("my\nKey"); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (global::ServiceType)1, Type = global::ServiceType, BaseType = global::System.Enum } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((global::ServiceType)1); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs index f9fa1e28..762c71f9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/KeyedServiceVerifyTests.Test_KeyedService_UnsignedIntegerTypes#LambdaHandler.g.verified.cs @@ -60,11 +60,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::IService, Name = serviceA, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (uint)42, Type = uint, BaseType = global::System.ValueType } } var arg0 = context.ServiceProvider.GetRequiredKeyedService((uint)42); - // ParameterInfo { Type = global::IService, Name = serviceB, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (ulong)42, Type = ulong, BaseType = global::System.ValueType } } var arg1 = context.ServiceProvider.GetRequiredKeyedService((ulong)42); - // ParameterInfo { Type = global::IService, Name = serviceC, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = (ushort)42, Type = ushort, BaseType = global::System.ValueType } } var arg2 = context.ServiceProvider.GetRequiredKeyedService((ushort)42); castHandler.Invoke(arg0, arg1, arg2); return Task.CompletedTask; @@ -76,4 +73,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs index e0d822a8..8aa525ce 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/MethodHandlerVerifyTests.Test_MethodHandler_BlockBody_InputDiKeyedServices#LambdaHandler.g.verified.cs @@ -70,11 +70,8 @@ Task InvocationDelegate(ILambdaInvocationContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = string, Name = input, Source = Event, IsNullable = False, IsOptional = False} var arg0 = context.GetRequiredEvent(); - // ParameterInfo { Type = global::Amazon.Lambda.Core.ILambdaContext, Name = context, Source = HostContext, IsNullable = False, IsOptional = False} var arg1 = context; - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetRequiredKeyedService("key"); var response = castHandler.Invoke(arg0, arg1, arg2); if (context.Features.Get() is not IResponseFeature responseFeature) @@ -91,4 +88,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_BaseMethodCall#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs index a69230bb..644f8dfa 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDi#LambdaHandler.g.verified.cs @@ -51,7 +51,6 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0); return response; @@ -62,4 +61,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs index 6d1e010a..a932e538 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MethodHandler_AsyncAndDiAndReturnUnexpectedType#LambdaHandler.g.verified.cs @@ -51,7 +51,6 @@ Delegate handler async Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); await castHandler.Invoke(arg0); return true; @@ -62,4 +61,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs index a32ea31c..0d32cd15 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_MultipleCalls#LambdaHandler.g.verified.cs @@ -55,7 +55,7 @@ Task OnInit(ILambdaLifecycleContext context) return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnInitBuilder OnInitInterceptor1( this ILambdaOnInitBuilder application, @@ -68,15 +68,13 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnInitBuilder OnInitInterceptor2( this ILambdaOnInitBuilder application, @@ -89,9 +87,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -102,4 +98,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs index 96e4ef39..a017c4a9 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = True} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs index dae86938..048e53f4 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs @@ -55,15 +55,10 @@ Task OnInit(ILambdaLifecycleContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = token, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service1, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("key1"); - // ParameterInfo { Type = global::IService?, Name = service2, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key2", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetKeyedService("key2"); - // ParameterInfo { Type = global::IService, Name = service3, Source = Service, IsNullable = False, IsOptional = False} var arg3 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service4, Source = Service, IsNullable = True, IsOptional = False} var arg4 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1, arg2, arg3, arg4); return response; @@ -74,4 +69,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs index 5facbd97..1a72ed5e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnInitVerifyTests.Test_OnInit_PrimitiveInput#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnInit(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs deleted file mode 100644 index aacf46ab..00000000 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_BaseMethodCall#LambdaHandler.g.verified.cs +++ /dev/null @@ -1,43 +0,0 @@ -//HintName: LambdaHandler.g.cs -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - -#nullable enable - -namespace System.Runtime.CompilerServices -{ - using System.CodeDom.Compiler; - - [global::System.CodeDom.Compiler.GeneratedCode("MinimalLambda.SourceGenerators", "REPLACED")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace MinimalLambda.Generated -{ - using System; - using System.Runtime.CompilerServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Extensions.DependencyInjection; - using MinimalLambda; - using MinimalLambda.Builder; - - file static class Utilities - { - internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; - } -} \ No newline at end of file diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs index 1498df42..c415e702 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_MultipleCalls#LambdaHandler.g.verified.cs @@ -55,7 +55,7 @@ Task OnShutdown(ILambdaLifecycleContext context) return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnShutdownBuilder OnShutdownInterceptor1( this ILambdaOnShutdownBuilder application, @@ -68,15 +68,13 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; } } - + [InterceptsLocation(1, "REPLACED")] internal static ILambdaOnShutdownBuilder OnShutdownInterceptor2( this ILambdaOnShutdownBuilder application, @@ -89,9 +87,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -102,4 +98,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs index 2221e934..59200694 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_NullableValueAndReferenceInputs#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string?, Name = x, Source = Service, IsNullable = True, IsOptional = False} var arg0 = context.ServiceProvider.GetService(); - // ParameterInfo { Type = global::IService?, Name = y, Source = Service, IsNullable = True, IsOptional = False} var arg1 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs index eb933b99..06950c8e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_OneOfEachPossibleKindOfInput#LambdaHandler.g.verified.cs @@ -55,15 +55,10 @@ Task OnShutdown(ILambdaLifecycleContext context) { throw new InvalidOperationException($"Unable to resolve service referenced by {nameof(FromKeyedServicesAttribute)}. The service provider doesn't support keyed services."); } - // ParameterInfo { Type = global::System.Threading.CancellationToken, Name = token, Source = CancellationToken, IsNullable = False, IsOptional = False} var arg0 = context.CancellationToken; - // ParameterInfo { Type = global::IService, Name = service1, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key1", Type = string, BaseType = object } } var arg1 = context.ServiceProvider.GetRequiredKeyedService("key1"); - // ParameterInfo { Type = global::IService?, Name = service2, Source = KeyedService, IsNullable = True, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "key2", Type = string, BaseType = object } } var arg2 = context.ServiceProvider.GetKeyedService("key2"); - // ParameterInfo { Type = global::IService, Name = service3, Source = Service, IsNullable = False, IsOptional = False} var arg3 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = global::IService?, Name = service4, Source = Service, IsNullable = True, IsOptional = False} var arg4 = context.ServiceProvider.GetService(); var response = castHandler.Invoke(arg0, arg1, arg2, arg3, arg4); return response; @@ -74,4 +69,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs index 2ab54a3a..8c26aea1 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/OnShutdownVerifyTests.Test_OnShutdown_PrimitiveInput#LambdaHandler.g.verified.cs @@ -51,9 +51,7 @@ Delegate handler Task OnShutdown(ILambdaLifecycleContext context) { - // ParameterInfo { Type = string, Name = x, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - // ParameterInfo { Type = int, Name = y, Source = Service, IsNullable = False, IsOptional = False} var arg1 = context.ServiceProvider.GetRequiredService(); var response = castHandler.Invoke(arg0, arg1); return response; @@ -64,4 +62,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs index 9fc2dc04..f244b32c 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_AbstractMiddleware#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddleware2Resolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs index 28145279..e57e42be 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ComplexRealWorldScenario#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache0 = NotCached; // string private int _cache3 = NotCached; // global::IDataService? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,27 +76,23 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : throw new InvalidOperationException("Parameter 'name' of type 'string' must be provided in args"); - - // ParameterInfo { Type = global::ILogger, Name = logger, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "primary", Type = string, BaseType = object } } + var arg1 = context.ServiceProvider.GetRequiredKeyedService("primary"); - - // ParameterInfo { Type = global::IMetrics, Name = metrics, Source = Service, IsNullable = False, IsOptional = False} + var arg2 = context.ServiceProvider.GetRequiredService(); - - // ParameterInfo { Type = global::IDataService?, Name = dataService, Source = Service, IsNullable = True, IsOptional = False} + var arg3 = _cache3 >= 0 ? (global::IDataService?)_args[_cache3] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0, arg1, arg2, arg3); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -127,4 +123,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs index 2f0fd7c9..259fd390 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_ConstructorWithArgs#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // global::IService - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService)_args[_cache0] : context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs index 7753f04a..c93faa51 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromArgumentsAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // string - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : throw new InvalidOperationException("Parameter 'apiKey' of type 'string' must be provided in args"); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs index 4c232acb..adf3093e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromKeyedServicesAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,9 +67,8 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "myKey", Type = string, BaseType = object } } var arg0 = context.ServiceProvider.GetRequiredKeyedService("myKey"); - + return new global::MyLambdaMiddleware(arg0); } } @@ -79,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs index dd30689a..d2e6b09f 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_FromServicesAttribute#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,9 +67,8 @@ private class MyLambdaMiddlewareResolver0 internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) { - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0); } } @@ -79,4 +78,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs index 31f82828..f0719af3 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IAsyncDisposable#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return async context => @@ -77,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs index d5208f9d..3b0c8cf5 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_IDisposable#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return async context => @@ -77,4 +77,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs index aca3e663..7eef3be6 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MixedParameterSources#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache2 = NotCached; // string private int _cache3 = NotCached; // global::IMetrics? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,27 +76,23 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::ILogger, Name = logger, Source = Service, IsNullable = False, IsOptional = False} var arg0 = context.ServiceProvider.GetRequiredService(); - - // ParameterInfo { Type = global::ICache, Name = cache, Source = KeyedService, IsNullable = False, IsOptional = False, KeyedServiceKeyInfo { DisplayValue = "cache", Type = string, BaseType = object } } + var arg1 = context.ServiceProvider.GetRequiredKeyedService("cache"); - - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} + var arg2 = _cache2 >= 0 ? (string)_args[_cache2] : throw new InvalidOperationException("Parameter 'apiKey' of type 'string' must be provided in args"); - - // ParameterInfo { Type = global::IMetrics?, Name = metrics, Source = Service, IsNullable = True, IsOptional = False} + var arg3 = _cache3 >= 0 ? (global::IMetrics?)_args[_cache3] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0, arg1, arg2, arg3); } - + private void BuildResolutionCache() { _cache2 = FromServices; @@ -127,4 +123,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs index 5e76e9ab..403974bd 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_MultipleConstructorsAndOneWithMiddlewareConstructor#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs index 0d9446e3..cae4cafc 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_NullableParameter#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // global::IService? - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = global::IService?, Name = service, Source = Service, IsNullable = True, IsOptional = False} var arg0 = _cache0 >= 0 ? (global::IService?)_args[_cache0] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs index 3c086068..1bd48759 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_OptionalParameterWithDefaultValue#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -67,7 +67,7 @@ private class MyLambdaMiddlewareResolver0 private readonly object[] _args; private int _cache0 = NotCached; // string - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -75,15 +75,14 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = name, Source = Service, IsNullable = False, IsOptional = True} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : context.ServiceProvider.GetService(); - + return new global::MyLambdaMiddleware(arg0); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -110,4 +109,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs index 5e76e9ab..403974bd 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_Simple#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -76,4 +76,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs index 23db5e79..960c6d7e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/Snapshots/UseMiddlewareTVerifyTests.Test_MiddlewareClass_WithArgsArray#LambdaHandler.g.verified.cs @@ -47,7 +47,7 @@ params object[] args where T : ILambdaMiddleware { var resolver = new MyLambdaMiddlewareResolver0(args); - + builder.Use(next => { return context => @@ -68,7 +68,7 @@ private class MyLambdaMiddlewareResolver0 private int _cache0 = NotCached; // string private int _cache1 = NotCached; // global::IService - + internal MyLambdaMiddlewareResolver0(object[] args) => _args = args; internal global::MyLambdaMiddleware Create(ILambdaInvocationContext context) @@ -76,21 +76,19 @@ private class MyLambdaMiddlewareResolver0 if (!_cacheBuilt) BuildResolutionCache(); - // ParameterInfo { Type = string, Name = apiKey, Source = Service, IsNullable = False, IsOptional = False} var arg0 = _cache0 >= 0 ? (string)_args[_cache0] : context.ServiceProvider.GetRequiredService(); - - // ParameterInfo { Type = global::IService, Name = service, Source = Service, IsNullable = False, IsOptional = False} + var arg1 = _cache1 >= 0 ? (global::IService)_args[_cache1] : context.ServiceProvider.GetRequiredService(); - + return new global::MyLambdaMiddleware(arg0, arg1); } - + private void BuildResolutionCache() { _cache0 = FromServices; @@ -121,4 +119,4 @@ file static class Utilities { internal static T Cast(Delegate d, T _) where T : Delegate => (T)d; } -} \ No newline at end of file +} diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs index 18a94741..f42534a8 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/ExpressionLambdaVerifyTests.cs @@ -18,28 +18,8 @@ await GeneratorTestHelpers.Verify( lambda.Handle(Task (ILambdaInvocationContext context) => Task.CompletedTask); await lambda.RunAsync(); - """ - ); - - [Fact] - public async Task Test_ExpressionLambda_MainOverload_DeserializerSerializer_NoOp() => - await GeneratorTestHelpers.Verify( - """ - using System.IO; - using System.Threading.Tasks; - using Amazon.Lambda.Core; - using MinimalLambda; - using MinimalLambda.Builder; - using Microsoft.Extensions.Hosting; - - var builder = LambdaApplication.CreateBuilder(); - - var lambda = builder.Build(); - - lambda.Handle(Task (ILambdaInvocationContext context) => Task.CompletedTask); - - await lambda.RunAsync(); - """ + """, + 0 ); [Fact] @@ -282,6 +262,30 @@ public interface IService """ ); + [Fact] + public async Task Test_ExpressionLambda_NullableInput_ReturnNullableValueType() => + await GeneratorTestHelpers.Verify( + """ + using MinimalLambda; + using MinimalLambda.Builder; + using Microsoft.Extensions.Hosting; + + var builder = LambdaApplication.CreateBuilder(); + var lambda = builder.Build(); + + lambda.MapHandler(([FromEvent] int? input, IService service) => service.GetMessage()); + + await lambda.RunAsync(); + + public struct MyStruct { } + + public interface IService + { + MyStruct? GetMessage(); + } + """ + ); + // Additional handler type not shown in the examples - generic handlers with complex custom // types [Fact] @@ -456,4 +460,26 @@ await GeneratorTestHelpers.Verify( await lambda.RunAsync(); """ ); + + [Fact] + public async Task Test_ExpressionLambda_OptionalInjectedParam() => + await GeneratorTestHelpers.Verify( + """ + using MinimalLambda; + using MinimalLambda.Builder; + using Microsoft.Extensions.Hosting; + + var builder = LambdaApplication.CreateBuilder(); + var lambda = builder.Build(); + + lambda.MapHandler(string? (IService? service = default) => service.GetMessage()); + + await lambda.RunAsync(); + + public interface IService + { + string? GetMessage(); + } + """ + ); } diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs index 90d7e8f9..2f06c99e 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnInitVerifyTests.cs @@ -22,7 +22,8 @@ await GeneratorTestHelpers.Verify( ); await lambda.RunAsync(); - """ + """, + 0 ); [Fact] diff --git a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs index 9332452b..cd6c2804 100644 --- a/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs +++ b/tests/MinimalLambda.SourceGenerators.UnitTests/VerifyTests/OnShutdownVerifyTests.cs @@ -22,7 +22,8 @@ await GeneratorTestHelpers.Verify( ); await lambda.RunAsync(); - """ + """, + 0 ); [Fact]