| File: RuntimeSource\Configuration.Binder\Specs\InterceptorInfo.cs | Web Access |
| Project: src\src\Tools\ConfigurationSchemaGenerator\ConfigurationSchemaGenerator.csproj (ConfigurationSchemaGenerator) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Text; using SourceGenerators; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed record InterceptorInfo { public required MethodsToGen MethodsToGen { get; init; } public required ImmutableEquatableArray<TypedInterceptorInvocationInfo>? ConfigBinder_Bind_instance { get; init; } public required ImmutableEquatableArray<TypedInterceptorInvocationInfo>? ConfigBinder_Bind_instance_BinderOptions { get; init; } public required ImmutableEquatableArray<TypedInterceptorInvocationInfo>? ConfigBinder_Bind_key_instance { get; init; } public required ImmutableEquatableArray<InvocationLocationInfo>? ConfigBinder { get; init; } public required ImmutableEquatableArray<InvocationLocationInfo>? OptionsBuilderExt { get; init; } public required ImmutableEquatableArray<InvocationLocationInfo>? ServiceCollectionExt { get; init; } public IEnumerable<InvocationLocationInfo>? GetInfo(MethodsToGen interceptor) { Debug.Assert((MethodsToGen.ConfigBinder_Bind & interceptor) is 0); ImmutableEquatableArray<InvocationLocationInfo>? infoList; if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & interceptor) is not 0) { infoList = ConfigBinder; } else if ((MethodsToGen.OptionsBuilderExt_Any & interceptor) is not 0) { infoList = OptionsBuilderExt; } else { Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & interceptor) is not 0); infoList = ServiceCollectionExt; } return infoList?.Where(i => i.Interceptor == interceptor); } internal sealed class Builder { private TypedInterceptorInfoBuilder? _configBinder_InfoBuilder_Bind_instance; private TypedInterceptorInfoBuilder? _configBinder_InfoBuilder_Bind_instance_BinderOptions; private TypedInterceptorInfoBuilder? _configBinder_InfoBuilder_Bind_key_instance; private List<InvocationLocationInfo>? _interceptors_configBinder; private List<InvocationLocationInfo>? _interceptors_OptionsBuilderExt; private List<InvocationLocationInfo>? _interceptors_serviceCollectionExt; public MethodsToGen MethodsToGen { get; set; } public void RegisterInterceptor_ConfigBinder_Bind(MethodsToGen overload, ComplexTypeSpec type, IInvocationOperation invocation) { Debug.Assert((MethodsToGen.ConfigBinder_Bind & overload) is not 0); switch (overload) { case MethodsToGen.ConfigBinder_Bind_instance: RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_instance); break; case MethodsToGen.ConfigBinder_Bind_instance_BinderOptions: RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_instance_BinderOptions); break; case MethodsToGen.ConfigBinder_Bind_key_instance: RegisterInterceptor(ref _configBinder_InfoBuilder_Bind_key_instance); break; } MethodsToGen |= overload; void RegisterInterceptor(ref TypedInterceptorInfoBuilder? infoBuilder) { infoBuilder ??= new TypedInterceptorInfoBuilder(); infoBuilder.RegisterInterceptor(overload, type, invocation); } } public void RegisterInterceptor(MethodsToGen overload, IInvocationOperation operation) { Debug.Assert((MethodsToGen.ConfigBinder_Bind & overload) is 0); if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & overload) is not 0) { RegisterInterceptor(ref _interceptors_configBinder); } else if ((MethodsToGen.OptionsBuilderExt_Any & overload) is not 0) { RegisterInterceptor(ref _interceptors_OptionsBuilderExt); } else { Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0); RegisterInterceptor(ref _interceptors_serviceCollectionExt); } MethodsToGen |= overload; void RegisterInterceptor(ref List<InvocationLocationInfo>? infoList) { infoList ??= new List<InvocationLocationInfo>(); infoList.Add(new InvocationLocationInfo(overload, operation)); } } public InterceptorInfo ToIncrementalValue() => new InterceptorInfo { MethodsToGen = MethodsToGen, ConfigBinder = _interceptors_configBinder?.ToImmutableEquatableArray(), OptionsBuilderExt = _interceptors_OptionsBuilderExt?.ToImmutableEquatableArray(), ServiceCollectionExt = _interceptors_serviceCollectionExt?.ToImmutableEquatableArray(), ConfigBinder_Bind_instance = _configBinder_InfoBuilder_Bind_instance?.ToIncrementalValue(), ConfigBinder_Bind_instance_BinderOptions = _configBinder_InfoBuilder_Bind_instance_BinderOptions?.ToIncrementalValue(), ConfigBinder_Bind_key_instance = _configBinder_InfoBuilder_Bind_key_instance?.ToIncrementalValue(), }; } } internal sealed class TypedInterceptorInfoBuilder { private readonly Dictionary<ComplexTypeSpec, TypedInterceptorInvocationInfo.Builder> _invocationInfoBuilderCache = new(); public void RegisterInterceptor(MethodsToGen overload, ComplexTypeSpec type, IInvocationOperation invocation) { if (!_invocationInfoBuilderCache.TryGetValue(type, out TypedInterceptorInvocationInfo.Builder? invocationInfoBuilder)) { _invocationInfoBuilderCache[type] = invocationInfoBuilder = new TypedInterceptorInvocationInfo.Builder(overload, type); } invocationInfoBuilder.RegisterInvocation(invocation); } public ImmutableEquatableArray<TypedInterceptorInvocationInfo>? ToIncrementalValue() => _invocationInfoBuilderCache.Values .Select(b => b.ToIncrementalValue()) .ToImmutableEquatableArray(); } public sealed record TypedInterceptorInvocationInfo(ComplexTypeSpec TargetType, ImmutableEquatableArray<InvocationLocationInfo> Locations) { public sealed class Builder(MethodsToGen Overload, ComplexTypeSpec TargetType) { private readonly List<InvocationLocationInfo> _infoList = new(); public void RegisterInvocation(IInvocationOperation invocation) => _infoList.Add(new InvocationLocationInfo(Overload, invocation)); public TypedInterceptorInvocationInfo ToIncrementalValue() => new( TargetType, Locations: _infoList.ToImmutableEquatableArray()); } } public sealed record InvocationLocationInfo { public InvocationLocationInfo(MethodsToGen interceptor, IInvocationOperation invocation) { Debug.Assert(BinderInvocation.IsBindingOperation(invocation)); if (invocation.Syntax is not InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax memberAccessExprSyntax }) { const string InvalidInvocationErrMsg = "The invocation should have been validated upstream when selecting invocations to emit interceptors for."; throw new ArgumentException(InvalidInvocationErrMsg, nameof(invocation)); } SyntaxTree operationSyntaxTree = invocation.Syntax.SyntaxTree; TextSpan memberNameSpan = memberAccessExprSyntax.Name.Span; FileLinePositionSpan linePosSpan = operationSyntaxTree.GetLineSpan(memberNameSpan); Interceptor = interceptor; LineNumber = linePosSpan.StartLinePosition.Line + 1; CharacterNumber = linePosSpan.StartLinePosition.Character + 1; FilePath = GetInterceptorFilePath(); // Use the same logic used by the interceptors API for resolving the source mapped value of a path. // https://github.com/dotnet/roslyn/blob/f290437fcc75dad50a38c09e0977cce13a64f5ba/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs#L1063-L1064 string GetInterceptorFilePath() { SourceReferenceResolver? sourceReferenceResolver = invocation.SemanticModel?.Compilation.Options.SourceReferenceResolver; return sourceReferenceResolver?.NormalizePath(operationSyntaxTree.FilePath, baseFilePath: null) ?? operationSyntaxTree.FilePath; } } public MethodsToGen Interceptor { get; } public string FilePath { get; } public int LineNumber { get; } public int CharacterNumber { get; } } }