generated/runtime/BuildTime/Models/PsProxyTypes.cs

/*---------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using static Commvault.Powershell.Runtime.PowerShell.PsProxyOutputExtensions;
using static Commvault.Powershell.Runtime.PowerShell.PsProxyTypeExtensions;

namespace Commvault.Powershell.Runtime.PowerShell
{
    internal class ProfileGroup
    {
        public string ProfileName { get; }
        public Variant[] Variants { get; }
        public string ProfileFolder { get; }

        public ProfileGroup(Variant[] variants, string profileName = NoProfiles)
        {
            ProfileName = profileName;
            Variants = variants;
            ProfileFolder = ProfileName != NoProfiles ? ProfileName : String.Empty;
        }
    }

    internal class VariantGroup
    {
        public string ModuleName { get; }
        public string CmdletName { get; }
        public string CmdletVerb { get; }
        public string CmdletNoun { get; }
        public string ProfileName { get; }
        public Variant[] Variants { get; }
        public ParameterGroup[] ParameterGroups { get; }
        public ComplexInterfaceInfo[] ComplexInterfaceInfos { get; }

        public string[] Aliases { get; }
        public PSTypeName[] OutputTypes { get; }
        public bool SupportsShouldProcess { get; }
        public bool SupportsPaging { get; }
        public string DefaultParameterSetName { get; }
        public bool HasMultipleVariants { get; }
        public PsHelpInfo HelpInfo { get; }
        public bool IsGenerated { get; }
        public bool IsInternal { get; }

        public string OutputFolder { get; }
        public string FileName { get; }
        public string FilePath { get; }

        public CommentInfo CommentInfo { get; }

        public VariantGroup(string moduleName, string cmdletName, Variant[] variants, string outputFolder, string profileName = NoProfiles, bool isTest = false, bool isInternal = false)
        {
            ModuleName = moduleName;
            CmdletName = cmdletName;
            var cmdletNameParts = CmdletName.Split('-');
            CmdletVerb = cmdletNameParts.First();
            CmdletNoun = cmdletNameParts.Last();
            ProfileName = profileName;
            Variants = variants;
            ParameterGroups = Variants.ToParameterGroups().OrderBy(pg => pg.OrderCategory).ThenByDescending(pg => pg.IsMandatory).ToArray();
            var aliasDuplicates = ParameterGroups.SelectMany(pg => pg.Aliases)
                //https://stackoverflow.com/a/18547390/294804
                .GroupBy(a => a).Where(g => g.Count() > 1).Select(g => g.Key).ToArray();
            if (aliasDuplicates.Any())
            {
                throw new ParsingMetadataException($"The alias(es) [{String.Join(", ", aliasDuplicates)}] are defined on multiple parameters for cmdlet '{CmdletName}', which is not supported.");
            }
            ComplexInterfaceInfos = ParameterGroups.Where(pg => !pg.DontShow && pg.IsComplexInterface).OrderBy(pg => pg.ParameterName).Select(pg => pg.ComplexInterfaceInfo).ToArray();

            Aliases = Variants.SelectMany(v => v.Attributes).ToAliasNames().ToArray();
            OutputTypes = Variants.SelectMany(v => v.Info.OutputType).Where(ot => ot.Type != null).GroupBy(ot => ot.Type).Select(otg => otg.First()).ToArray();
            SupportsShouldProcess = Variants.Any(v => v.SupportsShouldProcess);
            SupportsPaging = Variants.Any(v => v.SupportsPaging);
            DefaultParameterSetName = DetermineDefaultParameterSetName();
            HasMultipleVariants = Variants.Length > 1;
            HelpInfo = Variants.Select(v => v.HelpInfo).FirstOrDefault() ?? new PsHelpInfo();
            IsGenerated = Variants.All(v => v.Attributes.OfType<GeneratedAttribute>().Any());
            IsInternal = isInternal;

            OutputFolder = outputFolder;
            FileName = $"{CmdletName}{(isTest ? ".Tests" : String.Empty)}.ps1";
            FilePath = Path.Combine(OutputFolder, FileName);

            CommentInfo = new CommentInfo(this);
        }

        private string DetermineDefaultParameterSetName()
        {
            var defaultParameterSet = Variants
                .Select(v => v.Metadata.DefaultParameterSetName)
                .LastOrDefault(dpsn => dpsn.IsValidDefaultParameterSetName());

            if (String.IsNullOrEmpty(defaultParameterSet))
            {
                var variantParamCountGroups = Variants
                    .Select(v => (
                        variant: v.VariantName,
                        paramCount: v.CmdletOnlyParameters.Count(p => p.IsMandatory),
                        isSimple: v.CmdletOnlyParameters.Where(p => p.IsMandatory).All(p => p.ParameterType.IsPsSimple())))
                    .GroupBy(vpc => vpc.isSimple)
                    .ToArray();
                var variantParameterCounts = (variantParamCountGroups.Any(g => g.Key) ? variantParamCountGroups.Where(g => g.Key) : variantParamCountGroups).SelectMany(g => g).ToArray();
                var smallestParameterCount = variantParameterCounts.Min(vpc => vpc.paramCount);
                defaultParameterSet = variantParameterCounts.First(vpc => vpc.paramCount == smallestParameterCount).variant;
            }

            return defaultParameterSet;
        }
    }

    internal class Variant
    {
        public string CmdletName { get; }
        public string VariantName { get; }
        public CommandInfo Info { get; }
        public CommandMetadata Metadata { get; }
        public PsHelpInfo HelpInfo { get; }
        public bool HasParameterSets { get; }
        public bool IsFunction { get; }
        public string PrivateModuleName { get; }
        public string PrivateCmdletName { get; }
        public bool SupportsShouldProcess { get; }
        public bool SupportsPaging { get; }

        public Attribute[] Attributes { get; }
        public Parameter[] Parameters { get; }
        public Parameter[] CmdletOnlyParameters { get; }
        public bool IsInternal { get; }
        public bool IsDoNotExport { get; }
        public string[] Profiles { get; }

        public Variant(string cmdletName, string variantName, CommandInfo info, CommandMetadata metadata, bool hasParameterSets = false, PsHelpInfo helpInfo = null)
        {
            CmdletName = cmdletName;
            VariantName = variantName;
            Info = info;
            HelpInfo = helpInfo ?? new PsHelpInfo();
            Metadata = metadata;
            HasParameterSets = hasParameterSets;
            IsFunction = Info.CommandType == CommandTypes.Function;
            PrivateModuleName = Info.Source;
            PrivateCmdletName = Metadata.Name;
            SupportsShouldProcess = Metadata.SupportsShouldProcess;
            SupportsPaging = Metadata.SupportsPaging;

            Attributes = this.ToAttributes();
            Parameters = this.ToParameters().OrderBy(p => p.OrderCategory).ThenByDescending(p => p.IsMandatory).ToArray();
            IsInternal = Attributes.OfType<InternalExportAttribute>().Any();
            IsDoNotExport = Attributes.OfType<DoNotExportAttribute>().Any();
            CmdletOnlyParameters = Parameters.Where(p => !p.Categories.Any(c => c == ParameterCategory.Azure || c == ParameterCategory.Runtime)).ToArray();
            Profiles = Attributes.OfType<ProfileAttribute>().SelectMany(pa => pa.Profiles).ToArray();
        }
    }

    internal class ParameterGroup
    {
        public string ParameterName { get; }
        public Parameter[] Parameters { get; }

        public string[] VariantNames { get; }
        public string[] AllVariantNames { get; }
        public bool HasAllVariants { get; }
        public Type ParameterType { get; }
        public string Description { get; }

        public string[] Aliases { get; }
        public bool HasValidateNotNull { get; }
        public CompleterInfo CompleterInfo { get; }
        public DefaultInfo DefaultInfo { get; }
        public bool HasDefaultInfo { get; }
        public ParameterCategory OrderCategory { get; }
        public bool DontShow { get; }
        public bool IsMandatory { get; }
        public bool SupportsWildcards { get; }
        public bool IsComplexInterface { get; }
        public ComplexInterfaceInfo ComplexInterfaceInfo { get; }
        public InfoAttribute InfoAttribute { get; }

        public int? FirstPosition { get; }
        public bool ValueFromPipeline { get; }
        public bool ValueFromPipelineByPropertyName { get; }
        public bool IsInputType { get; }

        public ParameterGroup(string parameterName, Parameter[] parameters, string[] allVariantNames)
        {
            ParameterName = parameterName;
            Parameters = parameters;

            VariantNames = Parameters.Select(p => p.VariantName).ToArray();
            AllVariantNames = allVariantNames;
            HasAllVariants = VariantNames.Any(vn => vn == AllParameterSets) || !AllVariantNames.Except(VariantNames).Any();
            var types = Parameters.Select(p => p.ParameterType).Distinct().ToArray();
            if (types.Length > 1)
            {
                throw new ParsingMetadataException($"The parameter '{ParameterName}' has multiple parameter types [{String.Join(", ", types.Select(t => t.Name))}] defined, which is not supported.");
            }
            ParameterType = types.First();
            Description = Parameters.Select(p => p.Description).FirstOrDefault(d => !String.IsNullOrEmpty(d)).EmptyIfNull();

            Aliases = Parameters.SelectMany(p => p.Attributes).ToAliasNames().ToArray();
            HasValidateNotNull = Parameters.SelectMany(p => p.Attributes.OfType<ValidateNotNullAttribute>()).Any();
            CompleterInfo = Parameters.Select(p => p.CompleterInfoAttribute).FirstOrDefault()?.ToCompleterInfo()
                            ?? Parameters.Select(p => p.ArgumentCompleterAttribute).FirstOrDefault()?.ToCompleterInfo();
            DefaultInfo = Parameters.Select(p => p.DefaultInfoAttribute).FirstOrDefault()?.ToDefaultInfo(this)
                            ?? Parameters.Select(p => p.DefaultValueAttribute).FirstOrDefault(dv => dv != null)?.ToDefaultInfo(this);
            HasDefaultInfo = DefaultInfo != null && !String.IsNullOrEmpty(DefaultInfo.Script);
            // When DefaultInfo is present, force all parameters from this group to be optional.
            if (HasDefaultInfo)
            {
                foreach (var parameter in Parameters)
                {
                    parameter.IsMandatory = false;
                }
            }
            OrderCategory = Parameters.Select(p => p.OrderCategory).Distinct().DefaultIfEmpty(ParameterCategory.Body).Min();
            DontShow = Parameters.All(p => p.DontShow);
            IsMandatory = HasAllVariants && Parameters.Any(p => p.IsMandatory);
            SupportsWildcards = Parameters.Any(p => p.SupportsWildcards);
            IsComplexInterface = Parameters.Any(p => p.IsComplexInterface);
            ComplexInterfaceInfo = Parameters.Where(p => p.IsComplexInterface).Select(p => p.ComplexInterfaceInfo).FirstOrDefault();
            InfoAttribute = Parameters.Select(p => p.InfoAttribute).First();

            FirstPosition = Parameters.Select(p => p.Position).FirstOrDefault(p => p != null);
            ValueFromPipeline = Parameters.Any(p => p.ValueFromPipeline);
            ValueFromPipelineByPropertyName = Parameters.Any(p => p.ValueFromPipelineByPropertyName);
            IsInputType = ValueFromPipeline || ValueFromPipelineByPropertyName;
        }
    }

    internal class Parameter
    {
        public string VariantName { get; }
        public string ParameterName { get; }
        public ParameterMetadata Metadata { get; }
        public PsParameterHelpInfo HelpInfo { get; }
        public Type ParameterType { get; }

        public Attribute[] Attributes { get; }
        public ParameterCategory[] Categories { get; }
        public ParameterCategory OrderCategory { get; }
        public PSDefaultValueAttribute DefaultValueAttribute { get; }
        public DefaultInfoAttribute DefaultInfoAttribute { get; }
        public ParameterAttribute ParameterAttribute { get; }
        public bool SupportsWildcards { get; }
        public CompleterInfoAttribute CompleterInfoAttribute { get; }
        public ArgumentCompleterAttribute ArgumentCompleterAttribute { get; }

        public bool ValueFromPipeline { get; }
        public bool ValueFromPipelineByPropertyName { get; }
        public int? Position { get; }
        public bool DontShow { get; }
        public bool IsMandatory { get; set; }

        public InfoAttribute InfoAttribute { get; }
        public ComplexInterfaceInfo ComplexInterfaceInfo { get; }
        public bool IsComplexInterface { get; }
        public string Description { get; }

        public Parameter(string variantName, string parameterName, ParameterMetadata metadata, PsParameterHelpInfo helpInfo = null)
        {
            VariantName = variantName;
            ParameterName = parameterName;
            Metadata = metadata;
            HelpInfo = helpInfo ?? new PsParameterHelpInfo();

            Attributes = Metadata.Attributes.ToArray();
            ParameterType = Attributes.OfType<ExportAsAttribute>().FirstOrDefault()?.Type ?? Metadata.ParameterType;
            Categories = Attributes.OfType<CategoryAttribute>().SelectMany(ca => ca.Categories).Distinct().ToArray();
            OrderCategory = Categories.DefaultIfEmpty(ParameterCategory.Body).Min();
            DefaultValueAttribute = Attributes.OfType<PSDefaultValueAttribute>().FirstOrDefault();
            DefaultInfoAttribute = Attributes.OfType<DefaultInfoAttribute>().FirstOrDefault();
            ParameterAttribute = Attributes.OfType<ParameterAttribute>().FirstOrDefault(pa => pa.ParameterSetName == VariantName || pa.ParameterSetName == AllParameterSets);
            if (ParameterAttribute == null)
            {
                throw new ParsingMetadataException($"The variant '{VariantName}' has multiple parameter sets defined, which is not supported.");
            }
            SupportsWildcards = Attributes.OfType<SupportsWildcardsAttribute>().Any();
            CompleterInfoAttribute = Attributes.OfType<CompleterInfoAttribute>().FirstOrDefault();
            ArgumentCompleterAttribute = Attributes.OfType<ArgumentCompleterAttribute>().FirstOrDefault();

            ValueFromPipeline = ParameterAttribute.ValueFromPipeline;
            ValueFromPipelineByPropertyName = ParameterAttribute.ValueFromPipelineByPropertyName;
            Position = ParameterAttribute.Position == Int32.MinValue ? (int?)null : ParameterAttribute.Position;
            DontShow = ParameterAttribute.DontShow;
            IsMandatory = ParameterAttribute.Mandatory;

            var complexParameterName = ParameterName.ToUpperInvariant();
            var complexMessage = $"{Environment.NewLine}To construct, see NOTES section for {complexParameterName} properties and create a hash table.";
            var description = ParameterAttribute.HelpMessage.NullIfEmpty() ?? HelpInfo.Description.NullIfEmpty() ?? InfoAttribute?.Description.NullIfEmpty() ?? String.Empty;
            // Remove the complex type message as it will be reinserted if this is a complex type
            description = description.NormalizeNewLines().Replace(complexMessage, String.Empty).Replace(complexMessage.ToPsSingleLine(), String.Empty);
            // Make an InfoAttribute for processing only if one isn't provided
            InfoAttribute = Attributes.OfType<InfoAttribute>().FirstOrDefault() ?? new InfoAttribute { PossibleTypes = new[] { ParameterType.Unwrap() }, Required = IsMandatory };
            // Set the description if the InfoAttribute does not have one since they are exported without a description
            InfoAttribute.Description = String.IsNullOrEmpty(InfoAttribute.Description) ? description : InfoAttribute.Description;
            ComplexInterfaceInfo = InfoAttribute.ToComplexInterfaceInfo(complexParameterName, ParameterType, true);
            IsComplexInterface = ComplexInterfaceInfo.IsComplexInterface;
            Description = $"{description}{(IsComplexInterface ? complexMessage : String.Empty)}";
        }
    }

    internal class ComplexInterfaceInfo
    {
        public InfoAttribute InfoAttribute { get; }

        public string Name { get; }
        public Type Type { get; }
        public bool Required { get; }
        public bool ReadOnly { get; }
        public string Description { get; }
        
        public ComplexInterfaceInfo[] NestedInfos { get; }
        public bool IsComplexInterface { get; }

        public ComplexInterfaceInfo(string name, Type type, InfoAttribute infoAttribute, bool? required, List<Type> seenTypes)
        {
            Name = name;
            Type = type;
            InfoAttribute = infoAttribute;

            Required = required ?? InfoAttribute.Required;
            ReadOnly = InfoAttribute.ReadOnly;
            Description = InfoAttribute.Description.ToPsSingleLine();

            var unwrappedType = Type.Unwrap();
            var hasBeenSeen = seenTypes?.Contains(unwrappedType) ?? false;
            (seenTypes ?? (seenTypes = new List<Type>())).Add(unwrappedType);
            NestedInfos = hasBeenSeen ? new ComplexInterfaceInfo[]{} :
                unwrappedType.GetInterfaces()
                .Concat(InfoAttribute.PossibleTypes)
                .SelectMany(pt => pt.GetProperties()
                    .SelectMany(pi => pi.GetCustomAttributes(true).OfType<InfoAttribute>()
                        .Select(ia => ia.ToComplexInterfaceInfo(pi.Name, pi.PropertyType, seenTypes: seenTypes))))
                .Where(cii => !cii.ReadOnly).OrderByDescending(cii => cii.Required).ToArray();
            // https://stackoverflow.com/a/503359/294804
            var associativeArrayInnerType = Type.GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAssociativeArray<>))
                ?.GetTypeInfo().GetGenericArguments().First();
            if (!hasBeenSeen && associativeArrayInnerType != null)
            {
                var anyInfo = new InfoAttribute { Description = "This indicates any property can be added to this object." };
                NestedInfos = NestedInfos.Prepend(anyInfo.ToComplexInterfaceInfo("(Any)", associativeArrayInnerType)).ToArray();
            }
            IsComplexInterface = NestedInfos.Any();
        }
    }

    internal class CommentInfo
    {
        public string Description { get; }
        public string Synopsis { get; }

        public string[] Examples { get; }
        public string[] Inputs { get; }
        public string[] Outputs { get; }

        public string OnlineVersion { get; }
        public string[] RelatedLinks { get; }

        private const string HelpLinkPrefix = @"https://docs.microsoft.com/en-us/powershell/module/";

        public CommentInfo(VariantGroup variantGroup)
        {
            var helpInfo = variantGroup.HelpInfo;
            Description = variantGroup.Variants.SelectMany(v => v.Attributes).OfType<DescriptionAttribute>().FirstOrDefault()?.Description.NullIfEmpty()
                          ?? helpInfo.Description.EmptyIfNull();
            // If there is no Synopsis, PowerShell may put in the Syntax string as the Synopsis. This seems unintended, so we remove the Synopsis in this situation.
            var synopsis = helpInfo.Synopsis.EmptyIfNull().Trim().StartsWith(variantGroup.CmdletName) ? String.Empty : helpInfo.Synopsis;
            Synopsis = synopsis.NullIfEmpty() ?? Description;

            Examples = helpInfo.Examples.Select(rl => rl.Code).ToArray();

            Inputs = (variantGroup.ParameterGroups.Where(pg => pg.IsInputType).Select(pg => pg.ParameterType.FullName).ToArray().NullIfEmpty() ??
                      helpInfo.InputTypes.Where(it => it.Name.NullIfWhiteSpace() != null).Select(it => it.Name).ToArray())
                .Where(i => i != "None").Distinct().OrderBy(i => i).ToArray();
            Outputs = (variantGroup.OutputTypes.Select(ot => ot.Type.FullName).ToArray().NullIfEmpty() ??
                       helpInfo.OutputTypes.Where(it => it.Name.NullIfWhiteSpace() != null).Select(ot => ot.Name).ToArray())
                .Where(o => o != "None").Distinct().OrderBy(o => o).ToArray();

            OnlineVersion = helpInfo.OnlineVersion?.Uri.NullIfEmpty() ?? $@"{HelpLinkPrefix}{variantGroup.ModuleName.ToLowerInvariant()}/{variantGroup.CmdletName.ToLowerInvariant()}";
            RelatedLinks = helpInfo.RelatedLinks.Select(rl => rl.Text).ToArray();
        }
    }

    internal class CompleterInfo
    {
        public string Name { get; }
        public string Description { get; }
        public string Script { get; }
        public Type Type { get; }
        public bool IsTypeCompleter { get; }

        public CompleterInfo(CompleterInfoAttribute infoAttribute)
        {
            Name = infoAttribute.Name;
            Description = infoAttribute.Description;
            Script = infoAttribute.Script;
        }

        public CompleterInfo(ArgumentCompleterAttribute completerAttribute)
        {
            Script = completerAttribute.ScriptBlock?.ToString();
            if (completerAttribute.Type != null)
            {
                Type = completerAttribute.Type;
                IsTypeCompleter = true;
            }
        }
    }

    internal class DefaultInfo
    {
        public string Name { get; }
        public string Description { get; }
        public string Script { get; }
        public ParameterGroup ParameterGroup { get; }

        public DefaultInfo(DefaultInfoAttribute infoAttribute, ParameterGroup parameterGroup)
        {
            Name = infoAttribute.Name;
            Description = infoAttribute.Description;
            Script = infoAttribute.Script;
            ParameterGroup = parameterGroup;
        }

        public DefaultInfo(PSDefaultValueAttribute defaultValueAttribute, ParameterGroup parameterGroup)
        {
            Description = defaultValueAttribute.Help;
            ParameterGroup = parameterGroup;
            if (defaultValueAttribute.Value != null)
            {
                Script = defaultValueAttribute.Value.ToString();
            }
        }
    }

    internal static class PsProxyTypeExtensions
    {
        public const string NoProfiles = "__NoProfiles";

        public static bool IsValidDefaultParameterSetName(this string parameterSetName) =>
            !String.IsNullOrEmpty(parameterSetName) && parameterSetName != AllParameterSets;

        public static Variant[] ToVariants(this CommandInfo info, PsHelpInfo helpInfo)
        {
            var metadata = new CommandMetadata(info);
            var privateCmdletName = metadata.Name.Split('!').First();
            var parts = privateCmdletName.Split('_');
            return parts.Length > 1
                ? new[] { new Variant(parts[0], parts[1], info, metadata, helpInfo: helpInfo) }
                // Process multiple parameter sets, so we declare a variant per parameter set.
                : info.ParameterSets.Select(ps => new Variant(privateCmdletName, ps.Name, info, metadata, true, helpInfo)).ToArray();
        }

        public static Variant[] ToVariants(this CmdletAndHelpInfo info) => info.CommandInfo.ToVariants(info.HelpInfo);

        public static Variant[] ToVariants(this CommandInfo info, PSObject helpInfo = null) => info.ToVariants(helpInfo?.ToPsHelpInfo());

        public static Parameter[] ToParameters(this Variant variant)
        {
            var parameters = variant.Metadata.Parameters.AsEnumerable();
            var parameterHelp = variant.HelpInfo.Parameters.AsEnumerable();
            if (variant.HasParameterSets)
            {
                parameters = parameters.Where(p => p.Value.ParameterSets.Keys.Any(k => k == variant.VariantName || k == AllParameterSets));
                parameterHelp = parameterHelp.Where(ph => !ph.ParameterSetNames.Any() || ph.ParameterSetNames.Any(psn => psn == variant.VariantName || psn == AllParameterSets));
            }
            return parameters.Select(p => new Parameter(variant.VariantName, p.Key, p.Value, parameterHelp.FirstOrDefault(ph => ph.Name == p.Key))).ToArray();
        }

        public static Attribute[] ToAttributes(this Variant variant) => variant.IsFunction
            ? ((FunctionInfo)variant.Info).ScriptBlock.Attributes.ToArray()
            : variant.Metadata.CommandType.GetCustomAttributes(false).Cast<Attribute>().ToArray();

        public static IEnumerable<ParameterGroup> ToParameterGroups(this Variant[] variants)
        {
            var allVariantNames = variants.Select(vg => vg.VariantName).ToArray();
            return variants
                .SelectMany(v => v.Parameters)
                .GroupBy(p => p.ParameterName, StringComparer.InvariantCultureIgnoreCase)
                .Select(pg => new ParameterGroup(pg.Key, pg.Select(p => p).ToArray(), allVariantNames));
        }

        public static ComplexInterfaceInfo ToComplexInterfaceInfo(this InfoAttribute infoAttribute, string name, Type type, bool? required = null, List<Type> seenTypes = null)
            => new ComplexInterfaceInfo(name, type, infoAttribute, required, seenTypes);

        public static CompleterInfo ToCompleterInfo(this CompleterInfoAttribute infoAttribute) => new CompleterInfo(infoAttribute);
        public static CompleterInfo ToCompleterInfo(this ArgumentCompleterAttribute completerAttribute) => new CompleterInfo(completerAttribute);

        public static DefaultInfo ToDefaultInfo(this DefaultInfoAttribute infoAttribute, ParameterGroup parameterGroup) => new DefaultInfo(infoAttribute, parameterGroup);
        public static DefaultInfo ToDefaultInfo(this PSDefaultValueAttribute defaultValueAttribute, ParameterGroup parameterGroup) => new DefaultInfo(defaultValueAttribute, parameterGroup);
    }
}