Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CompletionCompleters.cs9263 linesDownload Raw Back to CommandCompletion
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Buffers;5using System.Collections;6using System.Collections.Concurrent;7using System.Collections.Generic;8using System.Collections.ObjectModel;9using System.Data;10using System.Diagnostics.CodeAnalysis;11using System.Globalization;12using System.IO;13using System.Linq;14using System.Management.Automation.Internal;15using System.Management.Automation.Language;16using System.Management.Automation.Provider;17using System.Management.Automation.Runspaces;18using System.Reflection;19using System.Runtime.InteropServices;20using System.Text;21using System.Text.RegularExpressions;22using System.Threading;23 24using Microsoft.Management.Infrastructure;25using Microsoft.Management.Infrastructure.Options;26using Microsoft.PowerShell;27using Microsoft.PowerShell.Cim;28using Microsoft.PowerShell.Commands;29using Microsoft.PowerShell.Commands.Internal.Format;30 31namespace System.Management.Automation32{33    /// <summary>34    /// </summary>35    public static class CompletionCompleters36    {37        static CompletionCompleters()38        {39            AppDomain.CurrentDomain.AssemblyLoad += UpdateTypeCacheOnAssemblyLoad;40        }41 42        private static void UpdateTypeCacheOnAssemblyLoad(object sender, AssemblyLoadEventArgs args)43        {44            // Just null out the cache - we'll rebuild it the next time someone tries to complete a type.45            // We could rebuild it now, but we could be loading multiple assemblies (e.g. dependent assemblies)46            // and there is no sense in rebuilding anything until we're done loading all of the assemblies.47            Interlocked.Exchange(ref s_typeCache, null);48        }49 50        #region Command Names51 52        /// <summary>53        /// </summary>54        /// <param name="commandName"></param>55        /// <returns></returns>56        public static IEnumerable<CompletionResult> CompleteCommand(string commandName)57        {58            return CompleteCommand(commandName, null);59        }60 61        /// <summary>62        /// </summary>63        /// <param name="commandName"></param>64        /// <param name="moduleName"></param>65        /// <param name="commandTypes"></param>66        /// <returns></returns>67        public static IEnumerable<CompletionResult> CompleteCommand(string commandName, string moduleName, CommandTypes commandTypes = CommandTypes.All)68        {69            var runspace = Runspace.DefaultRunspace;70            if (runspace == null)71            {72                // No runspace, just return no results.73                return CommandCompletion.EmptyCompletionResult;74            }75 76            var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace));77            var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext;78            return CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = helper, ExecutionContext = executionContext }, moduleName, commandTypes);79        }80 81        internal static List<CompletionResult> CompleteCommand(CompletionContext context)82        {83            return CompleteCommand(context, null);84        }85 86        private static List<CompletionResult> CompleteCommand(CompletionContext context, string moduleName, CommandTypes types = CommandTypes.All)87        {88            var addAmpersandIfNecessary = IsAmpersandNeeded(context, false);89 90            string commandName = context.WordToComplete;91            string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref commandName);92 93            List<CompletionResult> commandResults = null;94 95            if (commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) == -1)96            {97                // The name to complete is neither module qualified nor is it a relative/rooted file path.98 99                Ast lastAst = null;100                if (context.RelatedAsts != null && context.RelatedAsts.Count > 0)101                {102                    lastAst = context.RelatedAsts.Last();103                }104 105                commandResults = ExecuteGetCommandCommand(useModulePrefix: false);106 107                if (lastAst != null)108                {109                    // We need to add the wildcard to the end so the regex is built correctly.110                    commandName += "*";111 112                    // Search the asts for function definitions that we might be calling113                    var findFunctionsVisitor = new FindFunctionsVisitor();114                    while (lastAst.Parent != null)115                    {116                        lastAst = lastAst.Parent;117                    }118 119                    lastAst.Visit(findFunctionsVisitor);120 121                    WildcardPattern commandNamePattern = WildcardPattern.Get(commandName, WildcardOptions.IgnoreCase);122                    foreach (var defn in findFunctionsVisitor.FunctionDefinitions)123                    {124                        if (commandNamePattern.IsMatch(defn.Name)125                            && !commandResults.Any(cr => cr.CompletionText.Equals(defn.Name, StringComparison.OrdinalIgnoreCase)))126                        {127                            // Results found in the current script are prepended to show up at the top of the list.128                            commandResults.Insert(0, GetCommandNameCompletionResult(defn.Name, defn, addAmpersandIfNecessary, quote));129                        }130                    }131                }132            }133            else134            {135                // If there is a single \, we might be looking for a module/snapin qualified command136                var indexOfFirstColon = commandName.IndexOf(':');137                var indexOfFirstBackslash = commandName.IndexOf('\\');138                if (indexOfFirstBackslash > 0 && (indexOfFirstBackslash < indexOfFirstColon || indexOfFirstColon == -1))139                {140                    // First try the name before the backslash as a module name.141                    // Use the exact module name provided by the user142                    moduleName = commandName.Substring(0, indexOfFirstBackslash);143                    commandName = commandName.Substring(indexOfFirstBackslash + 1);144 145                    commandResults = ExecuteGetCommandCommand(useModulePrefix: true);146                }147            }148 149            return commandResults;150 151            List<CompletionResult> ExecuteGetCommandCommand(bool useModulePrefix)152            {153                var powershell = context.Helper154                    .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))155                    .AddParameter("All")156                    .AddParameter("Name", commandName + "*");157 158                if (moduleName != null)159                {160                    powershell.AddParameter("Module", moduleName);161                }162 163                if (!types.Equals(CommandTypes.All))164                {165                    powershell.AddParameter("CommandType", types);166                }167 168                // Exception is ignored, the user simply does not get any completion results if the pipeline fails169                Exception exceptionThrown;170                var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);171 172                if (commandInfos == null || commandInfos.Count == 0)173                {174                    powershell.Commands.Clear();175                    powershell176                        .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))177                        .AddParameter("All")178                        .AddParameter("Name", commandName)179                        .AddParameter("UseAbbreviationExpansion");180 181                    if (moduleName != null)182                    {183                        powershell.AddParameter("Module", moduleName);184                    }185 186                    if (!types.Equals(CommandTypes.All))187                    {188                        powershell.AddParameter("CommandType", types);189                    }190 191                    commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);192                }193 194                List<CompletionResult> completionResults = null;195 196                if (commandInfos != null && commandInfos.Count > 1)197                {198                    // OrderBy is using stable sorting199                    var sortedCommandInfos = commandInfos.Order(new CommandNameComparer());200                    completionResults = MakeCommandsUnique(sortedCommandInfos, useModulePrefix, addAmpersandIfNecessary, quote);201                }202                else203                {204                    completionResults = MakeCommandsUnique(commandInfos, useModulePrefix, addAmpersandIfNecessary, quote);205                }206 207                return completionResults;208            }209        }210 211        private static readonly HashSet<string> s_keywordsToExcludeFromAddingAmpersand212            = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { nameof(TokenKind.InlineScript), nameof(TokenKind.Configuration) };213 214        internal static CompletionResult GetCommandNameCompletionResult(string name, object command, bool addAmpersandIfNecessary, string quote)215        {216            string syntax = name, listItem = name;217 218            var commandInfo = command as CommandInfo;219            if (commandInfo != null)220            {221                try222                {223                    listItem = commandInfo.Name;224                    // This may require parsing a script, which could fail in a number of different ways225                    // (syntax errors, security exceptions, etc.)  If so, the name is fine for the tooltip.226                    syntax = commandInfo.Syntax;227                }228                catch (Exception)229                {230                }231            }232 233            syntax = string.IsNullOrEmpty(syntax) ? name : syntax;234            bool needAmpersand;235 236            if (CompletionHelpers.CompletionRequiresQuotes(name))237            {238                needAmpersand = quote == string.Empty && addAmpersandIfNecessary;239                string quoteInUse = quote == string.Empty ? "'" : quote;240                if (quoteInUse == "'")241                {242                    name = name.Replace("'", "''");243                }244                else245                {246                    name = name.Replace("`", "``");247                    name = name.Replace("$", "`$");248                }249 250                name = quoteInUse + name + quoteInUse;251            }252            else253            {254                needAmpersand = quote == string.Empty && addAmpersandIfNecessary &&255                                Tokenizer.IsKeyword(name) && !s_keywordsToExcludeFromAddingAmpersand.Contains(name);256                name = quote + name + quote;257            }258 259            // It's useless to call ForEach-Object (foreach) as the first command of a pipeline. For example:260            //     PS C:\> fore<tab>  --->   PS C:\> foreach   (expected, use as the keyword)261            //     PS C:\> fore<tab>  --->   PS C:\> & foreach (unexpected, ForEach-Object is seldom used as the first command of a pipeline)262            if (needAmpersand && name != SpecialVariables.@foreach)263            {264                name = "& " + name;265            }266 267            return new CompletionResult(name, listItem, CompletionResultType.Command, syntax);268        }269 270        internal static List<CompletionResult> MakeCommandsUnique(IEnumerable<PSObject> commandInfoPsObjs, bool includeModulePrefix, bool addAmpersandIfNecessary, string quote)271        {272            List<CompletionResult> results = new List<CompletionResult>();273            if (commandInfoPsObjs == null || !commandInfoPsObjs.Any())274            {275                return results;276            }277 278            var commandTable = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);279            foreach (var psobj in commandInfoPsObjs)280            {281                object baseObj = PSObject.Base(psobj);282                string name = null;283 284                var commandInfo = baseObj as CommandInfo;285                if (commandInfo != null)286                {287                    // Skip the private commands288                    if (commandInfo.Visibility == SessionStateEntryVisibility.Private) { continue; }289 290                    name = commandInfo.Name;291                    if (includeModulePrefix && !string.IsNullOrEmpty(commandInfo.ModuleName))292                    {293                        // The command might be a prefixed commandInfo that we get by importing a module with the -Prefix parameter, for example:294                        //    FooModule.psm1: Get-Foo295                        //    import-module FooModule -Prefix PowerShell296                        //    --> command 'Get-PowerShellFoo' in the global session state (prefixed commandInfo)297                        //        command 'Get-Foo' in the module session state (un-prefixed commandInfo)298                        // in that case, we should not add the module name qualification because it doesn't work299                        if (string.IsNullOrEmpty(commandInfo.Prefix) || !ModuleCmdletBase.IsPrefixedCommand(commandInfo))300                        {301                            name = commandInfo.ModuleName + "\\" + commandInfo.Name;302                        }303                    }304                }305                else306                {307                    name = baseObj as string;308                    if (name == null) { continue; }309                }310 311                object value;312                if (!commandTable.TryGetValue(name, out value))313                {314                    commandTable.Add(name, baseObj);315                }316                else317                {318                    var list = value as List<object>;319                    if (list != null)320                    {321                        list.Add(baseObj);322                    }323                    else324                    {325                        list = new List<object> { value, baseObj };326                        commandTable[name] = list;327                    }328                }329            }330 331            foreach (var keyValuePair in commandTable)332            {333                if (keyValuePair.Value is List<object> commandList)334                {335                    var modulesWithCommand = new HashSet<string>(StringComparer.OrdinalIgnoreCase);336                    var importedModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase);337                    var commandInfoList = new List<CommandInfo>(commandList.Count);338                    for (int i = 0; i < commandList.Count; i++)339                    {340                        if (commandList[i] is not CommandInfo commandInfo)341                        {342                            continue;343                        }344 345                        commandInfoList.Add(commandInfo);346                        if (commandInfo.CommandType == CommandTypes.Application)347                        {348                            continue;349                        }350 351                        modulesWithCommand.Add(commandInfo.ModuleName);352                        if ((commandInfo.CommandType == CommandTypes.Cmdlet && commandInfo.CommandMetadata.CommandType is not null)353                            || (commandInfo.CommandType is CommandTypes.Function or CommandTypes.Filter && commandInfo.Definition != string.Empty)354                            || (commandInfo.CommandType == CommandTypes.Alias && commandInfo.Definition is not null))355                        {356                            // Checks if the command or source module has been imported.357                            _ = importedModules.Add(commandInfo.ModuleName);358                        }359                    }360 361                    if (commandInfoList.Count == 0)362                    {363                        continue;364                    }365 366                    int moduleCount = modulesWithCommand.Count;367                    modulesWithCommand.Clear();368                    int index;369                    if (commandInfoList[0].CommandType == CommandTypes.Application370                        || importedModules.Count == 1371                        || moduleCount < 2)372                    {373                        // We can use the short name for this command because there's no ambiguity about which command it resolves to.374                        // If the first element is an application then we know there's no conflicting commands/aliases (because of the command precedence).375                        // If there's just 1 module imported then the short name refers to that module (and it will be the first element in the list)376                        // If there's less than 2 unique modules exporting that command then we can use the short name because it can only refer to that module.377                        index = 1;378                        results.Add(GetCommandNameCompletionResult(keyValuePair.Key, commandInfoList[0], addAmpersandIfNecessary, quote));379                        modulesWithCommand.Add(commandInfoList[0].ModuleName);380                    }381                    else382                    {383                        index = 0;384                    }385 386                    for (; index < commandInfoList.Count; index++)387                    {388                        CommandInfo commandInfo = commandInfoList[index];389                        if (commandInfo.CommandType == CommandTypes.Application)390                        {391                            results.Add(GetCommandNameCompletionResult(commandInfo.Definition, commandInfo, addAmpersandIfNecessary, quote));392                        }393                        else if (!string.IsNullOrEmpty(commandInfo.ModuleName) && modulesWithCommand.Add(commandInfo.ModuleName))394                        {395                            var name = commandInfo.ModuleName + "\\" + commandInfo.Name;396                            results.Add(GetCommandNameCompletionResult(name, commandInfo, addAmpersandIfNecessary, quote));397                        }398                    }399                }400                else401                {402                    // The first command might be an un-prefixed commandInfo that we get by importing a module with the -Prefix parameter,403                    // in that case, we should add the module name qualification because if the module is not in the module path, calling404                    // 'Get-Foo' directly doesn't work405                    string completionName = keyValuePair.Key;406                    if (!includeModulePrefix)407                    {408                        var commandInfo = keyValuePair.Value as CommandInfo;409                        if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix))410                        {411                            Diagnostics.Assert(!string.IsNullOrEmpty(commandInfo.ModuleName), "the module name should exist if commandInfo.Prefix is not an empty string");412                            if (!ModuleCmdletBase.IsPrefixedCommand(commandInfo))413                            {414                                completionName = commandInfo.ModuleName + "\\" + completionName;415                            }416                        }417                    }418 419                    results.Add(GetCommandNameCompletionResult(completionName, keyValuePair.Value, addAmpersandIfNecessary, quote));420                }421            }422 423            return results;424        }425 426        private sealed class FindFunctionsVisitor : AstVisitor427        {428            internal readonly List<FunctionDefinitionAst> FunctionDefinitions = new List<FunctionDefinitionAst>();429 430            public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)431            {432                FunctionDefinitions.Add(functionDefinitionAst);433                return AstVisitAction.Continue;434            }435        }436 437        #endregion Command Names438 439        #region Module Names440 441        internal static List<CompletionResult> CompleteModuleName(CompletionContext context, bool loadedModulesOnly, bool skipEditionCheck = false)442        {443            var wordToComplete = context.WordToComplete ?? string.Empty;444            var result = new List<CompletionResult>();445            var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete);446 447            // Indicates if we should search for modules where the last part of the name matches the input text448            // eg: Host<Tab> finds Microsoft.PowerShell.Host449            // If the user has entered a manual wildcard, or a module name that contains a "." we assume they only want results that matches the input exactly.450            bool shortNameSearch = wordToComplete.Length > 0 && !WildcardPattern.ContainsWildcardCharacters(wordToComplete) && !wordToComplete.Contains('.');451 452            if (!wordToComplete.EndsWith('*'))453            {454                wordToComplete += "*";455            }456 457            string[] moduleNames;458            WildcardPattern shortNamePattern;459            if (shortNameSearch)460            {461                moduleNames = new string[] { wordToComplete, "*." + wordToComplete };462                shortNamePattern = new WildcardPattern(wordToComplete, WildcardOptions.IgnoreCase);463            }464            else465            {466                moduleNames = new string[] { wordToComplete };467                shortNamePattern = null;468            }469 470            var powershell = context.Helper.AddCommandWithPreferenceSetting("Get-Module", typeof(GetModuleCommand)).AddParameter("Name", moduleNames);471            if (!loadedModulesOnly)472            {473                powershell.AddParameter("ListAvailable", true);474 475                // -SkipEditionCheck should only be set or apply to -ListAvailable476                if (skipEditionCheck)477                {478                    powershell.AddParameter("SkipEditionCheck", true);479                }480            }481 482            Collection<PSObject> psObjects = context.Helper.ExecuteCurrentPowerShell(out _);483 484            if (psObjects != null)485            {486                // When PowerShell is used interactively, completion is usually triggered by PSReadLine, with PSReadLine's SessionState487                // as the engine session state. In that case, results from the module search may contain a nested module of PSReadLine,488                // which should be filtered out below.489                // When the completion is triggered from global session state, such as when running 'TabExpansion2' from command line,490                // the module associated with engine session state will be null.491                //492                // Note that, it's intentional to not hard code the name 'PSReadLine' in the change, so that in case the tab completion493                // is triggered from within a different module, its nested modules can also be filtered out.494                HashSet<PSModuleInfo> nestedModulesToFilterOut = null;495                PSModuleInfo currentModule = context.ExecutionContext.EngineSessionState.Module;496                if (loadedModulesOnly && currentModule?.NestedModules.Count > 0)497                {498                    nestedModulesToFilterOut = new(currentModule.NestedModules);499                }500 501                var completedModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase);502                foreach (PSObject item in psObjects)503                {504                    var moduleInfo = (PSModuleInfo)item.BaseObject;505                    var completionText = moduleInfo.Name;506                    if (!completedModules.Add(completionText))507                    {508                        continue;509                    }510 511                    if (shortNameSearch512                        && completionText.Contains('.')513                        && !shortNamePattern.IsMatch(completionText.Substring(completionText.LastIndexOf('.') + 1))514                        && !shortNamePattern.IsMatch(completionText))515                    {516                        // This check is to make sure we don't return a module whose name only matches the user specified word in the middle.517                        // For example, when user completes with 'gmo power', we should not return 'Microsoft.PowerShell.Utility'.518                        continue;519                    }520 521                    if (nestedModulesToFilterOut is not null522                        && nestedModulesToFilterOut.Contains(moduleInfo))523                    {524                        continue;525                    }526 527                    var toolTip = "Description: " + moduleInfo.Description + "\r\nModuleType: "528                                  + moduleInfo.ModuleType.ToString() + "\r\nPath: "529                                  + moduleInfo.Path;530 531                    completionText = CompletionHelpers.QuoteCompletionText(completionText, quote);532 533                    result.Add(new CompletionResult(completionText, listItemText: moduleInfo.Name, CompletionResultType.ParameterValue, toolTip));534                }535            }536 537            return result;538        }539 540        #endregion Module Names541 542        #region Command Parameters543        private static readonly string[] s_parameterNamesOfImportDSCResource = { "Name", "ModuleName", "ModuleVersion" };544 545        internal static List<CompletionResult> CompleteCommandParameter(CompletionContext context)546        {547            string partialName = null;548            bool withColon = false;549            CommandAst commandAst = null;550            List<CompletionResult> result = new List<CompletionResult>();551 552            // Find the parameter ast, it will be near or at the end553            CommandParameterAst parameterAst = null;554            DynamicKeywordStatementAst keywordAst = null;555            for (int i = context.RelatedAsts.Count - 1; i >= 0; i--)556            {557                keywordAst ??= context.RelatedAsts[i] as DynamicKeywordStatementAst;558                parameterAst = (context.RelatedAsts[i] as CommandParameterAst);559                if (parameterAst != null) break;560            }561 562            if (parameterAst != null)563            {564                keywordAst = parameterAst.Parent as DynamicKeywordStatementAst;565            }566 567            // If parent is DynamicKeywordStatementAst - 'Import-DscResource',568            // then customize the auto completion results569            if (keywordAst != null && string.Equals(keywordAst.Keyword.Keyword, "Import-DscResource", StringComparison.OrdinalIgnoreCase)570                && !string.IsNullOrWhiteSpace(context.WordToComplete) && context.WordToComplete.StartsWith('-'))571            {572                var lastAst = context.RelatedAsts.Last();573                var wordToMatch = string.Concat(context.WordToComplete.AsSpan(1), "*");574                var pattern = WildcardPattern.Get(wordToMatch, WildcardOptions.IgnoreCase);575                var parameterNames = keywordAst.CommandElements.Where(static ast => ast is CommandParameterAst).Select(static ast => (ast as CommandParameterAst).ParameterName);576                foreach (var parameterName in s_parameterNamesOfImportDSCResource)577                {578                    if (pattern.IsMatch(parameterName) && !parameterNames.Contains(parameterName, StringComparer.OrdinalIgnoreCase))579                    {580                        string tooltip = "[String] " + parameterName;581                        result.Add(new CompletionResult("-" + parameterName, parameterName, CompletionResultType.ParameterName, tooltip));582                    }583                }584 585                if (result.Count > 0)586                {587                    context.ReplacementLength = context.WordToComplete.Length;588                    context.ReplacementIndex = lastAst.Extent.StartOffset;589                }590 591                return result;592            }593 594            bool bindPositionalParameters = true;595            if (parameterAst != null)596            {597                // Parent must be a command598                commandAst = (CommandAst)parameterAst.Parent;599                partialName = parameterAst.ParameterName;600                withColon = context.WordToComplete.EndsWith(':');601            }602            else603            {604                // No CommandParameterAst is found. It could be a StringConstantExpressionAst "-"605                if (context.RelatedAsts[context.RelatedAsts.Count - 1] is not StringConstantExpressionAst dashAst)606                    return result;607                if (!dashAst.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase))608                    return result;609 610                // Parent must be a command611                commandAst = (CommandAst)dashAst.Parent;612                partialName = string.Empty;613 614                // If the user tries to tab complete a new parameter in front of a positional argument like: dir -<Tab> C:\615                // the user may want to add the parameter name so we don't want to bind positional arguments616                if (commandAst is not null)617                {618                    foreach (var element in commandAst.CommandElements)619                    {620                        if (element.Extent.StartOffset > context.TokenAtCursor.Extent.StartOffset)621                        {622                            bindPositionalParameters = element is CommandParameterAst;623                            break;624                        }625                    }626                }627            }628 629            PseudoBindingInfo pseudoBinding = new PseudoParameterBinder()630                                                .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion, bindPositionalParameters);631            // The command cannot be found or it's not a cmdlet, not a script cmdlet, not a function.632            // Try completing as if it the parameter is a command argument for native command completion.633            if (pseudoBinding == null)634            {635                return CompleteCommandArgument(context);636            }637 638            switch (pseudoBinding.InfoType)639            {640                case PseudoBindingInfoType.PseudoBindingFail:641                    // The command is a cmdlet or script cmdlet. Binding failed642                    result = GetParameterCompletionResults(partialName, uint.MaxValue, pseudoBinding.UnboundParameters, withColon);643                    break;644                case PseudoBindingInfoType.PseudoBindingSucceed:645                    // The command is a cmdlet or script cmdlet. Binding succeeded.646                    result = GetParameterCompletionResults(partialName, pseudoBinding, parameterAst, withColon);647                    break;648            }649 650            if (result.Count == 0)651            {652                result = pseudoBinding.CommandName.Equals("Set-Location", StringComparison.OrdinalIgnoreCase)653                             ? new List<CompletionResult>(CompleteFilename(context, containerOnly: true, extension: null))654                             : new List<CompletionResult>(CompleteFilename(context));655            }656 657            return result;658        }659 660        /// <summary>661        /// Get the parameter completion results when the pseudo binding was successful.662        /// </summary>663        /// <param name="parameterName"></param>664        /// <param name="bindingInfo"></param>665        /// <param name="parameterAst"></param>666        /// <param name="withColon"></param>667        /// <returns></returns>668        private static List<CompletionResult> GetParameterCompletionResults(string parameterName, PseudoBindingInfo bindingInfo, CommandParameterAst parameterAst, bool withColon)669        {670            Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed");671            List<CompletionResult> result = new List<CompletionResult>();672            Assembly commandAssembly = null;673            if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo)674            {675                commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly;676            }677 678            if (parameterName == string.Empty)679            {680                result = GetParameterCompletionResults(681                    parameterName,682                    bindingInfo.ValidParameterSetsFlags,683                    bindingInfo.UnboundParameters,684                    withColon,685                    commandAssembly);686                return result;687            }688 689            if (bindingInfo.ParametersNotFound.Count > 0)690            {691                // The parameter name cannot be matched to any parameter692                if (bindingInfo.ParametersNotFound.Any(pAst => parameterAst.GetHashCode() == pAst.GetHashCode()))693                {694                    return result;695                }696            }697 698            if (bindingInfo.AmbiguousParameters.Count > 0)699            {700                // The parameter name is ambiguous. It's ignored in the pseudo binding, and we should search in the UnboundParameters701                if (bindingInfo.AmbiguousParameters.Any(pAst => parameterAst.GetHashCode() == pAst.GetHashCode()))702                {703                    result = GetParameterCompletionResults(704                        parameterName,705                        bindingInfo.ValidParameterSetsFlags,706                        bindingInfo.UnboundParameters,707                        withColon,708                        commandAssembly);709                }710 711                return result;712            }713 714            if (bindingInfo.DuplicateParameters.Count > 0)715            {716                // The parameter name is resolved to a parameter that is already bound. We search it in the BoundParameters717                if (bindingInfo.DuplicateParameters.Any(pAst => parameterAst.GetHashCode() == pAst.Parameter.GetHashCode()))718                {719                    result = GetParameterCompletionResults(720                        parameterName,721                        bindingInfo.ValidParameterSetsFlags,722                        bindingInfo.BoundParameters.Values,723                        withColon,724                        commandAssembly);725                }726 727                return result;728            }729 730            // The parameter should be bound in the pseudo binding during the named binding731            string matchedParameterName = null;732            foreach (KeyValuePair<string, AstParameterArgumentPair> entry in bindingInfo.BoundArguments)733            {734                switch (entry.Value.ParameterArgumentType)735                {736                    case AstParameterArgumentType.AstPair:737                        {738                            AstPair pair = (AstPair)entry.Value;739                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())740                            {741                                matchedParameterName = entry.Key;742                            }743                            else if (pair.ArgumentIsCommandParameterAst && pair.Argument.GetHashCode() == parameterAst.GetHashCode())744                            {745                                // The parameter name cannot be resolved to a parameter746                                return result;747                            }748                        }749 750                        break;751                    case AstParameterArgumentType.Fake:752                        {753                            FakePair pair = (FakePair)entry.Value;754                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())755                            {756                                matchedParameterName = entry.Key;757                            }758                        }759 760                        break;761                    case AstParameterArgumentType.Switch:762                        {763                            SwitchPair pair = (SwitchPair)entry.Value;764                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())765                            {766                                matchedParameterName = entry.Key;767                            }768                        }769 770                        break;771                    case AstParameterArgumentType.AstArray:772                    case AstParameterArgumentType.PipeObject:773                        break;774                }775 776                if (matchedParameterName != null)777                    break;778            }779 780            if (matchedParameterName is null)781            {782                // The pseudo binder has skipped a parameter783                // This will happen when completing parameters for commands with dynamic parameters.784                result = GetParameterCompletionResults(785                    parameterName,786                    bindingInfo.ValidParameterSetsFlags,787                    bindingInfo.UnboundParameters,788                    withColon,789                    commandAssembly);790                return result;791            }792 793            MergedCompiledCommandParameter param = bindingInfo.BoundParameters[matchedParameterName];794 795            WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);796            string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";797 798            string helpMessage = string.Empty;799            if (param.Parameter.CompiledAttributes is not null)800            {801                foreach (Attribute attr in param.Parameter.CompiledAttributes)802                {803                    if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))804                    {805                        helpMessage = $" - {attrHelpMessage}";806                        break;807                    }808                }809            }810 811            string colonSuffix = withColon ? ":" : string.Empty;812            if (pattern.IsMatch(matchedParameterName))813            {814                string completionText = $"-{matchedParameterName}{colonSuffix}";815                string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}";816                result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip));817            }818            else819            {820                // Process alias when there is partial input821                foreach (var alias in param.Parameter.Aliases)822                {823                    if (pattern.IsMatch(alias))824                    {825                        result.Add(new CompletionResult(826                            $"-{alias}{colonSuffix}",827                            alias,828                            CompletionResultType.ParameterName,829                            $"{parameterType}{alias}{helpMessage}"));830                    }831                }832            }833 834            return result;835        }836 837#nullable enable838        /// <summary>839        /// Try and get the help message text for the parameter attribute.840        /// </summary>841        /// <param name="attr">The attribute to check for the help message.</param>842        /// <param name="assembly">The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in.</param>843        /// <param name="message">The help message if it was found otherwise null.</param>844        /// <returns>True if the help message was set or false if not.></returns>845        private static bool TryGetParameterHelpMessage(846            ParameterAttribute attr,847            Assembly? assembly,848            [NotNullWhen(true)] out string? message)849        {850            message = null;851 852            if (attr.HelpMessage is not null)853            {854                message = attr.HelpMessage;855                return true;856            }857 858            if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null)859            {860                return false;861            }862 863            try864            {865                message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId);866                return message is not null;867            }868            catch (Exception)869            {870                return false;871            }872        }873#nullable disable874 875        /// <summary>876        /// Get the parameter completion results by using the given valid parameter sets and available parameters.877        /// </summary>878        /// <param name="parameterName"></param>879        /// <param name="validParameterSetFlags"></param>880        /// <param name="parameters"></param>881        /// <param name="withColon"></param>882        /// <param name="commandAssembly">Optional assembly used to lookup parameter help messages.</param>883        /// <returns></returns>884        private static List<CompletionResult> GetParameterCompletionResults(885            string parameterName,886            uint validParameterSetFlags,887            IEnumerable<MergedCompiledCommandParameter> parameters,888            bool withColon,889            Assembly commandAssembly = null)890        {891            var result = new List<CompletionResult>();892            var commonParamResult = new List<CompletionResult>();893            var pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);894            var colonSuffix = withColon ? ":" : string.Empty;895 896            bool addCommonParameters = true;897            foreach (MergedCompiledCommandParameter param in parameters)898            {899                bool inParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets;900                if (!inParameterSet)901                    continue;902 903                string name = param.Parameter.Name;904                string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";905                string helpMessage = null;906                bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase);907                List<CompletionResult> listInUse = isCommonParameter ? commonParamResult : result;908 909                if (pattern.IsMatch(name))910                {911                    // Then using functions to back dynamic keywords, we don't necessarily912                    // want all of the parameters to be shown to the user. Those that are marked913                    // DontShow will not be displayed. Also, if any of the parameters have914                    // don't show set, we won't show any of the common parameters either.915                    bool showToUser = true;916                    var compiledAttributes = param.Parameter.CompiledAttributes;917                    if (compiledAttributes != null && compiledAttributes.Count > 0)918                    {919                        foreach (var attr in compiledAttributes)920                        {921                            if (attr is ParameterAttribute pattr)922                            {923                                if (pattr.DontShow)924                                {925                                    showToUser = false;926                                    addCommonParameters = false;927                                    break;928                                }929 930                                if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))931                                {932                                    helpMessage = $" - {attrHelpMessage}";933                                }934                            }935                        }936                    }937 938                    if (showToUser)939                    {940                        string completionText = $"-{name}{colonSuffix}";941                        string tooltip = $"{type}{name}{helpMessage}";942                        listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName,943                                                           tooltip));944                    }945                }946                else if (parameterName != string.Empty)947                {948                    // Process alias when there is partial input949                    foreach (var alias in param.Parameter.Aliases)950                    {951                        if (pattern.IsMatch(alias))952                        {953                            listInUse.Add(new CompletionResult(954                                $"-{alias}{colonSuffix}",955                                alias,956                                CompletionResultType.ParameterName,957                                type + alias));958                        }959                    }960                }961            }962 963            // Add the common parameters to the results if expected.964            if (addCommonParameters)965            {966                result.AddRange(commonParamResult);967            }968 969            return result;970        }971 972        /// <summary>973        /// Get completion results for operators that start with <paramref name="wordToComplete"/>974        /// </summary>975        /// <param name="wordToComplete">The starting text of the operator to complete.</param>976        /// <returns>A list of completion results.</returns>977        public static List<CompletionResult> CompleteOperator(string wordToComplete)978        {979            if (wordToComplete.StartsWith('-'))980            {981                wordToComplete = wordToComplete.Substring(1);982            }983 984            return (from op in Tokenizer._operatorText985                    where op.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)986                    orderby op987                    select new CompletionResult("-" + op, op, CompletionResultType.ParameterName, GetOperatorDescription(op))).ToList();988        }989 990        private static string GetOperatorDescription(string op)991        {992            return ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).Assembly,993                                                          "System.Management.Automation.resources.TabCompletionStrings",994                                                          op + "OperatorDescription");995        }996 997        #endregion Command Parameters998 999        #region Command Arguments1000 1001        internal static List<CompletionResult> CompleteCommandArgument(CompletionContext context)1002        {1003            CommandAst commandAst = null;1004            List<CompletionResult> result = new List<CompletionResult>();1005 1006            // Find the expression ast. It should be at the end if there is one1007            ExpressionAst expressionAst = null;1008            MemberExpressionAst secondToLastMemberAst = null;1009            Ast lastAst = context.RelatedAsts.Last();1010 1011            expressionAst = lastAst as ExpressionAst;1012            if (expressionAst != null)1013            {1014                if (expressionAst.Parent is CommandAst)1015                {1016                    commandAst = (CommandAst)expressionAst.Parent;1017 1018                    if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(','))1019                    {1020                        context.WordToComplete = string.Empty;1021                        // BUGBUG context.CursorPosition = expressionAst.Extent.StartScriptPosition;1022                    }1023                    else if (commandAst.CommandElements.Count == 1 || context.WordToComplete == string.Empty)1024                    {1025                        expressionAst = null;1026                    }1027                    else if (commandAst.CommandElements.Count > 2)1028                    {1029                        var length = commandAst.CommandElements.Count;1030                        var index = 1;1031 1032                        for (; index < length; index++)1033                        {1034                            if (commandAst.CommandElements[index] == expressionAst)1035                                break;1036                        }1037 1038                        CommandElementAst secondToLastAst = null;1039                        if (index > 1)1040                        {1041                            secondToLastAst = commandAst.CommandElements[index - 1];1042                            secondToLastMemberAst = secondToLastAst as MemberExpressionAst;1043                        }1044 1045                        var partialPathAst = expressionAst as StringConstantExpressionAst;1046                        if (partialPathAst != null && secondToLastAst != null &&1047                            partialPathAst.StringConstantType == StringConstantType.BareWord &&1048                            secondToLastAst.Extent.EndLineNumber == partialPathAst.Extent.StartLineNumber &&1049                            secondToLastAst.Extent.EndColumnNumber == partialPathAst.Extent.StartColumnNumber &&1050                            partialPathAst.Value.AsSpan().IndexOfAny('\\', '/') == 0)1051                        {1052                            var secondToLastStringConstantAst = secondToLastAst as StringConstantExpressionAst;1053                            var secondToLastExpandableStringAst = secondToLastAst as ExpandableStringExpressionAst;1054                            var secondToLastArrayAst = secondToLastAst as ArrayLiteralAst;1055                            var secondToLastParamAst = secondToLastAst as CommandParameterAst;1056 1057                            if (secondToLastStringConstantAst != null || secondToLastExpandableStringAst != null)1058                            {1059                                var fullPath = ConcatenateStringPathArguments(secondToLastAst, partialPathAst.Value, context);1060                                expressionAst = secondToLastStringConstantAst != null1061                                                    ? (ExpressionAst)secondToLastStringConstantAst1062                                                    : (ExpressionAst)secondToLastExpandableStringAst;1063 1064                                context.ReplacementIndex = ((InternalScriptPosition)secondToLastAst.Extent.StartScriptPosition).Offset;1065                                context.ReplacementLength += ((InternalScriptPosition)secondToLastAst.Extent.EndScriptPosition).Offset - context.ReplacementIndex;1066                                context.WordToComplete = fullPath;1067                                // context.CursorPosition = secondToLastAst.Extent.StartScriptPosition;1068                            }1069                            else if (secondToLastArrayAst != null)1070                            {1071                                // Handle cases like: dir -Path .\cd, 'a b'\new<tab>1072                                var lastArrayElement = secondToLastArrayAst.Elements.LastOrDefault();1073                                var fullPath = ConcatenateStringPathArguments(lastArrayElement, partialPathAst.Value, context);1074                                if (fullPath != null)1075                                {1076                                    expressionAst = secondToLastArrayAst;1077 1078                                    context.ReplacementIndex = ((InternalScriptPosition)lastArrayElement.Extent.StartScriptPosition).Offset;1079                                    context.ReplacementLength += ((InternalScriptPosition)lastArrayElement.Extent.EndScriptPosition).Offset - context.ReplacementIndex;1080                                    context.WordToComplete = fullPath;1081                                }1082                            }1083                            else if (secondToLastParamAst != null)1084                            {1085                                // Handle cases like: dir -Path: .\cd, 'a b'\new<tab> || dir -Path: 'a b'\new<tab>1086                                var fullPath = ConcatenateStringPathArguments(secondToLastParamAst.Argument, partialPathAst.Value, context);1087                                if (fullPath != null)1088                                {1089                                    expressionAst = secondToLastParamAst.Argument;1090 1091                                    context.ReplacementIndex = ((InternalScriptPosition)secondToLastParamAst.Argument.Extent.StartScriptPosition).Offset;1092                                    context.ReplacementLength += ((InternalScriptPosition)secondToLastParamAst.Argument.Extent.EndScriptPosition).Offset - context.ReplacementIndex;1093                                    context.WordToComplete = fullPath;1094                                }1095                                else1096                                {1097                                    var arrayArgAst = secondToLastParamAst.Argument as ArrayLiteralAst;1098                                    if (arrayArgAst != null)1099                                    {1100                                        var lastArrayElement = arrayArgAst.Elements.LastOrDefault();1101                                        fullPath = ConcatenateStringPathArguments(lastArrayElement, partialPathAst.Value, context);1102                                        if (fullPath != null)1103                                        {1104                                            expressionAst = arrayArgAst;1105 1106                                            context.ReplacementIndex = ((InternalScriptPosition)lastArrayElement.Extent.StartScriptPosition).Offset;1107                                            context.ReplacementLength += ((InternalScriptPosition)lastArrayElement.Extent.EndScriptPosition).Offset - context.ReplacementIndex;1108                                            context.WordToComplete = fullPath;1109                                        }1110                                    }1111                                }1112                            }1113                        }1114                    }1115                }1116                else if (expressionAst.Parent is ArrayLiteralAst && expressionAst.Parent.Parent is CommandAst)1117                {1118                    commandAst = (CommandAst)expressionAst.Parent.Parent;1119 1120                    if (commandAst.CommandElements.Count == 1 || context.WordToComplete == string.Empty)1121                    {1122                        // dir -Path a.txt, b.txt <tab>1123                        expressionAst = null;1124                    }1125                    else1126                    {1127                        // dir -Path a.txt, b.txt c<tab>1128                        expressionAst = (ExpressionAst)expressionAst.Parent;1129                    }1130                }1131                else if (expressionAst.Parent is ArrayLiteralAst && expressionAst.Parent.Parent is CommandParameterAst)1132                {1133                    // Handle scenarios such as1134                    //      dir -Path: a.txt, <tab> || dir -Path: a.txt, b.txt <tab>1135                    commandAst = (CommandAst)expressionAst.Parent.Parent.Parent;1136                    if (context.WordToComplete == string.Empty)1137                    {1138                        // dir -Path: a.txt, b.txt <tab>1139                        expressionAst = null;1140                    }1141                    else1142                    {1143                        // dir -Path: a.txt, b<tab>1144                        expressionAst = (ExpressionAst)expressionAst.Parent;1145                    }1146                }1147                else if (expressionAst.Parent is CommandParameterAst && expressionAst.Parent.Parent is CommandAst)1148                {1149                    commandAst = (CommandAst)expressionAst.Parent.Parent;1150                    if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(','))1151                    {1152                        // dir -Path: a.txt,<tab>1153                        context.WordToComplete = string.Empty;1154                        // context.CursorPosition = expressionAst.Extent.StartScriptPosition;1155                    }1156                    else if (context.WordToComplete == string.Empty)1157                    {1158                        // Handle scenario like this: Set-ExecutionPolicy -Scope:CurrentUser <tab>1159                        expressionAst = null;1160                    }1161                }1162            }1163            else1164            {1165                var paramAst = lastAst as CommandParameterAst;1166                if (paramAst != null)1167                {1168                    commandAst = paramAst.Parent as CommandAst;1169                }1170                else1171                {1172                    commandAst = lastAst as CommandAst;1173                }1174            }1175 1176            if (commandAst == null)1177            {1178                // We don't know if this could be expanded into anything interesting1179                return result;1180            }1181 1182            PseudoBindingInfo pseudoBinding = new PseudoParameterBinder()1183                                                .DoPseudoParameterBinding(commandAst, null, null, PseudoParameterBinder.BindingType.ArgumentCompletion);1184 1185            do1186            {1187                // The command cannot be found, or it's NOT a cmdlet, NOT a script cmdlet and NOT a function1188                if (pseudoBinding == null)1189                    break;1190 1191                bool parsedArgumentsProvidesMatch = false;1192 1193                if (pseudoBinding.AllParsedArguments != null && pseudoBinding.AllParsedArguments.Count > 0)1194                {1195                    ArgumentLocation argLocation;1196                    bool treatAsExpression = false;1197 1198                    if (expressionAst != null)1199                    {1200                        treatAsExpression = true;

Showing the first 1,200 of 9263 lines. Download the file for the rest.