| File: Emitter\Helpers.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Configuration.Binder\gen\Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj (Microsoft.Extensions.Configuration.Binder.SourceGeneration) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Reflection; namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration { public sealed partial class ConfigurationBindingGenerator { private sealed partial class Emitter { internal static readonly AssemblyName s_assemblyName = typeof(ConfigurationBindingGenerator).Assembly.GetName(); private string? _emittedExtsTargetType; private enum InitializationKind { None = 0, SimpleAssignment = 1, AssignmentWithNullCheck = 2, Declaration = 3, } /// <summary> /// The type of defaulting for a property if it does not have a config entry. /// This should only be applied for "Get" cases, not "Bind" and is also conditioned /// on the source generated for a particular property as to whether it uses this value. /// Note this is different than "InitializationKind.Declaration" since it only applied to /// complex types and not arrays\enumerables. /// </summary> private enum ValueDefaulting { None = 0, /// <summary> /// Call the setter with the default value for the property's Type. /// </summary> CallSetter = 1, } private static class Expression { public const string configurationGetSection = "configuration.GetSection"; public const string sectionKey = "section.Key"; public const string sectionPath = "section.Path"; public const string sectionValue = "section.Value"; public static string GeneratedCodeAnnotation = $@"[GeneratedCode(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]"; } private static class TypeDisplayString { public const string NullableActionOfBinderOptions = "Action<BinderOptions>?"; public const string OptionsBuilderOfTOptions = $"global::Microsoft.Extensions.Options.OptionsBuilder<{Identifier.TOptions}>"; public const string HashSetOfString = "global::System.Collections.Generic.HashSet<string>"; public const string LazyHashSetOfString = "Lazy<global::System.Collections.Generic.HashSet<string>>"; public const string ListOfString = "global::System.Collections.Generic.List<string>"; } private static class Identifier { public const string binderOptions = nameof(binderOptions); public const string config = nameof(config); public const string configureBinder = nameof(configureBinder); public const string configureOptions = nameof(configureOptions); public const string configuration = nameof(configuration); public const string configSectionPath = nameof(configSectionPath); public const string defaultValue = nameof(defaultValue); public const string element = nameof(element); public const string enumValue = nameof(enumValue); public const string exception = nameof(exception); public const string key = nameof(key); public const string name = nameof(name); public const string instance = nameof(instance); public const string optionsBuilder = nameof(optionsBuilder); public const string originalCount = nameof(originalCount); public const string path = nameof(path); public const string section = nameof(section); public const string sectionKey = nameof(sectionKey); public const string services = nameof(services); public const string sp = nameof(sp); public const string temp = nameof(temp); public const string type = nameof(type); public const string typedObj = nameof(typedObj); public const string validateKeys = nameof(validateKeys); public const string value = nameof(value); public const string Add = nameof(Add); public const string AddSingleton = nameof(AddSingleton); public const string Any = nameof(Any); public const string Array = nameof(Array); public const string Bind = nameof(Bind); public const string BinderOptions = nameof(BinderOptions); public const string BindingExtensions = nameof(BindingExtensions); public const string ConfigurationChangeTokenSource = nameof(ConfigurationChangeTokenSource); public const string Configure = nameof(Configure); public const string CopyTo = nameof(CopyTo); public const string ContainsKey = nameof(ContainsKey); public const string Count = nameof(Count); public const string CultureInfo = nameof(CultureInfo); public const string CultureNotFoundException = nameof(CultureNotFoundException); public const string Enum = nameof(Enum); public const string ErrorOnUnknownConfiguration = nameof(ErrorOnUnknownConfiguration); public const string Exception = nameof(Exception); public const string Get = nameof(Get); public const string GetBinderOptions = nameof(GetBinderOptions); public const string GetChildren = nameof(GetChildren); public const string GetSection = nameof(GetSection); public const string GetValue = nameof(GetValue); public const string HasConfig = nameof(HasConfig); public const string HasValueOrChildren = nameof(HasValueOrChildren); public const string HasValue = nameof(HasValue); public const string IConfiguration = nameof(IConfiguration); public const string IConfigurationSection = nameof(IConfigurationSection); public const string ConfigurationSection = nameof(ConfigurationSection); public const string Int32 = "int"; public const string InterceptsLocation = nameof(InterceptsLocation); public const string InvalidOperationException = nameof(InvalidOperationException); public const string InvariantCulture = nameof(InvariantCulture); public const string IOptionsChangeTokenSource = nameof(IOptionsChangeTokenSource); public const string IServiceCollection = nameof(IServiceCollection); public const string Length = nameof(Length); public const string Name = nameof(Name); public const string NumberStyles = nameof(NumberStyles); public const string Parse = nameof(Parse); public const string Path = nameof(Path); public const string Resize = nameof(Resize); public const string Services = nameof(Services); public const string TOptions = nameof(TOptions); public const string TryCreate = nameof(TryCreate); public const string TryGetValue = nameof(TryGetValue); public const string Type = nameof(Type); public const string Uri = nameof(Uri); public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys); public const string TryGetConfigurationValue = nameof(TryGetConfigurationValue); public const string Value = nameof(Value); } private bool ShouldEmitMethods(MethodsToGen methods) => (_interceptorInfo.MethodsToGen & methods) != 0; private void EmitInterceptsLocationAnnotations(MethodsToGen overload) { IEnumerable<InvocationLocationInfo>? infoList = _interceptorInfo.GetInfo(overload); bool interceptsCalls = infoList is not null; // The only time a generated binding method won't have any locations to // intercept is when either of these methods are used as helpers for // other generated OptionsBuilder or ServiceCollection binding extensions. Debug.Assert(interceptsCalls || overload is MethodsToGen.ServiceCollectionExt_Configure_T_name_BinderOptions || overload is MethodsToGen.OptionsBuilderExt_Bind_T_BinderOptions); if (interceptsCalls) { EmitInterceptsLocationAnnotations(infoList!); } } private void EmitInterceptsLocationAnnotations(IEnumerable<InvocationLocationInfo> infoList) { if (ConfigurationBindingGenerator.InterceptorVersion == 0) { foreach (InvocationLocationInfo info in infoList) { _writer.WriteLine($@"[{Identifier.InterceptsLocation}(@""{info.FilePath}"", {info.LineNumber}, {info.CharacterNumber})]"); } } else { foreach (InvocationLocationInfo info in infoList) { _writer.WriteLine($@"[{Identifier.InterceptsLocation}({info.InterceptableLocationVersion}, ""{info.InterceptableLocationData}"")] // {info.InterceptableLocationGetDisplayLocation()}"); } } } private void EmitBindingExtStartRegion(string targetType) { Debug.Assert(_emittedExtsTargetType is null); EmitBlankLineIfRequired(); _emittedExtsTargetType = targetType; EmitBindingExtRegionText(isStart: true); _emitBlankLineBeforeNextStatement = false; } private void EmitBindingExtEndRegion() { Debug.Assert(_emittedExtsTargetType is not null); EmitBindingExtRegionText(isStart: false); _emittedExtsTargetType = null; _emitBlankLineBeforeNextStatement = true; } private void EmitBindingExtRegionText(bool isStart) { string endSource = isStart ? string.Empty : "end"; _writer.WriteLine($"#{endSource}region {_emittedExtsTargetType} extensions."); } /// <summary> /// Starts a block of source code. /// </summary> /// <param name="source">Source to write after the open brace.</param> public void EmitStartBlock(string? source = null) { if (source is not null) { _writer.WriteLine(source); } _writer.WriteLine("{"); _writer.Indentation++; } /// <summary> /// Ends a block of source code. /// </summary> /// <param name="source">Source to write before the close brace.</param> /// <param name="endBraceTrailingSource">Trailing source after the end brace, e.g. ";" to end an init statement.</param> public void EmitEndBlock(string? source = null, string? endBraceTrailingSource = null) { if (source is not null) { _writer.WriteLine(source); } string endBlockSource = endBraceTrailingSource is null ? "}" : $"}}{endBraceTrailingSource}"; _writer.Indentation--; _writer.WriteLine(endBlockSource); } private void EmitBlankLineIfRequired() { if (_emitBlankLineBeforeNextStatement) { _writer.WriteLine(); } _emitBlankLineBeforeNextStatement = true; } private void EmitCheckForNullArgument_WithBlankLine(string paramName, bool useThrowIfNullMethod, bool voidReturn = false) { if (voidReturn) { _writer.WriteLine($$""" if ({{paramName}} is null) { return; } """); } else { string throwIfNullExpr = useThrowIfNullMethod ? $"ArgumentNullException.ThrowIfNull({paramName});" : $$""" if ({{paramName}} is null) { throw new ArgumentNullException(nameof({{paramName}})); } """; _writer.WriteLine(throwIfNullExpr); } _writer.WriteLine(); } private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}"; private static string GetInitializeMethodDisplayString(ObjectSpec type) => $"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.IdentifierCompatibleSubstring}"; } } }