Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CompletionAnalysis.cs2944 linesDownload Raw Back to CommandCompletion
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections;6using System.Collections.Generic;7using System.Linq;8using System.Management.Automation.Language;9using System.Reflection;10using System.Text;11using System.Text.RegularExpressions;12using System.Management.Automation.Subsystem;13using System.Management.Automation.Subsystem.DSC;14 15namespace System.Management.Automation16{17    internal class CompletionContext18    {19        internal List<Ast> RelatedAsts { get; set; }20 21        // Only one of TokenAtCursor or TokenBeforeCursor is set22        // This is how we can tell if we're trying to complete part of something (like a member)23        // or complete an argument, where TokenBeforeCursor could be a parameter name.24        internal Token TokenAtCursor { get; set; }25 26        internal Token TokenBeforeCursor { get; set; }27 28        internal IScriptPosition CursorPosition { get; set; }29 30        internal PowerShellExecutionHelper Helper { get; set; }31 32        internal Hashtable Options { get; set; }33 34        internal Dictionary<string, ScriptBlock> CustomArgumentCompleters { get; set; }35 36        internal Dictionary<string, ScriptBlock> NativeArgumentCompleters { get; set; }37 38        internal string WordToComplete { get; set; }39 40        internal int ReplacementIndex { get; set; }41 42        internal int ReplacementLength { get; set; }43 44        internal ExecutionContext ExecutionContext { get; set; }45 46        internal PseudoBindingInfo PseudoBindingInfo { get; set; }47 48        internal TypeInferenceContext TypeInferenceContext { get; set; }49 50        internal bool GetOption(string option, bool @default)51        {52            if (Options == null || !Options.ContainsKey(option))53            {54                return @default;55            }56 57            return LanguagePrimitives.ConvertTo<bool>(Options[option]);58        }59    }60 61    internal class CompletionAnalysis62    {63        private readonly Ast _ast;64        private readonly Token[] _tokens;65        private readonly IScriptPosition _cursorPosition;66        private readonly Hashtable _options;67 68        internal CompletionAnalysis(Ast ast, Token[] tokens, IScriptPosition cursorPosition, Hashtable options)69        {70            _ast = ast;71            _tokens = tokens;72            _cursorPosition = cursorPosition;73            _options = options;74        }75 76        private static bool IsInterestingToken(Token token)77        {78            return token.Kind != TokenKind.NewLine && token.Kind != TokenKind.EndOfInput;79        }80 81        private static bool IsCursorWithinOrJustAfterExtent(IScriptPosition cursor, IScriptExtent extent)82        {83            return cursor.Offset > extent.StartOffset && cursor.Offset <= extent.EndOffset;84        }85 86        private static bool IsCursorRightAfterExtent(IScriptPosition cursor, IScriptExtent extent)87        {88            return cursor.Offset == extent.EndOffset;89        }90 91        private static bool IsCursorAfterExtentAndInTheSameLine(IScriptPosition cursor, IScriptExtent extent)92        {93            return cursor.Offset >= extent.EndOffset && extent.EndLineNumber == cursor.LineNumber;94        }95 96        private static bool IsCursorBeforeExtent(IScriptPosition cursor, IScriptExtent extent)97        {98            return cursor.Offset < extent.StartOffset;99        }100 101        private static bool IsCursorAfterExtent(IScriptPosition cursor, IScriptExtent extent)102        {103            return extent.EndOffset < cursor.Offset;104        }105 106        private static bool IsCursorOutsideOfExtent(IScriptPosition cursor, IScriptExtent extent)107        {108            return cursor.Offset < extent.StartOffset || cursor.Offset > extent.EndOffset;109        }110 111        internal readonly struct AstAnalysisContext112        {113            internal AstAnalysisContext(Token tokenAtCursor, Token tokenBeforeCursor, List<Ast> relatedAsts, int replacementIndex)114            {115                TokenAtCursor = tokenAtCursor;116                TokenBeforeCursor = tokenBeforeCursor;117                RelatedAsts = relatedAsts;118                ReplacementIndex = replacementIndex;119            }120 121            internal readonly Token TokenAtCursor;122            internal readonly Token TokenBeforeCursor;123            internal readonly List<Ast> RelatedAsts;124            internal readonly int ReplacementIndex;125        }126 127        internal static AstAnalysisContext ExtractAstContext(Ast inputAst, Token[] inputTokens, IScriptPosition cursor)128        {129            bool adjustLineAndColumn = false;130            IScriptPosition positionForAstSearch = cursor;131 132            Token tokenBeforeCursor = null;133            Token tokenAtCursor = InterstingTokenAtCursorOrDefault(inputTokens, cursor);134            if (tokenAtCursor == null)135            {136                tokenBeforeCursor = InterstingTokenBeforeCursorOrDefault(inputTokens, cursor);137                if (tokenBeforeCursor != null)138                {139                    positionForAstSearch = tokenBeforeCursor.Extent.EndScriptPosition;140                    adjustLineAndColumn = true;141                }142            }143            else144            {145                var stringExpandableToken = tokenAtCursor as StringExpandableToken;146                if (stringExpandableToken?.NestedTokens != null)147                {148                    tokenAtCursor = InterstingTokenAtCursorOrDefault(stringExpandableToken.NestedTokens, cursor) ?? stringExpandableToken;149                }150            }151 152            int replacementIndex = adjustLineAndColumn ? cursor.Offset : 0;153            List<Ast> relatedAsts = AstSearcher.FindAll(154                inputAst,155                ast => IsCursorWithinOrJustAfterExtent(positionForAstSearch, ast.Extent),156                searchNestedScriptBlocks: true).ToList();157 158            if (relatedAsts.Count == 0)159            {160                relatedAsts.Add(inputAst);161            }162 163            // If the last ast is an unnamed block that starts with "param" the cursor is inside a param block.164            // To avoid adding special handling to all the completers that look at the last ast, we remove it here because it's not useful for completion.165            if (relatedAsts[^1].Extent.Text.StartsWith("param", StringComparison.OrdinalIgnoreCase)166                && relatedAsts[^1] is NamedBlockAst namedBlock && namedBlock.Unnamed)167            {168                relatedAsts.RemoveAt(relatedAsts.Count - 1);169            }170 171            Diagnostics.Assert(tokenAtCursor == null || tokenBeforeCursor == null, "Only one of these tokens can be non-null");172 173            return new AstAnalysisContext(tokenAtCursor, tokenBeforeCursor, relatedAsts, replacementIndex);174        }175 176        internal CompletionContext CreateCompletionContext(PowerShell powerShell)177        {178            var typeInferenceContext = new TypeInferenceContext(powerShell);179            return InitializeCompletionContext(typeInferenceContext);180        }181 182        internal CompletionContext CreateCompletionContext(TypeInferenceContext typeInferenceContext)183        {184            return InitializeCompletionContext(typeInferenceContext);185        }186 187        private CompletionContext InitializeCompletionContext(TypeInferenceContext typeInferenceContext)188        {189            var astContext = ExtractAstContext(_ast, _tokens, _cursorPosition);190 191            typeInferenceContext.CurrentTypeDefinitionAst ??= Ast.GetAncestorTypeDefinitionAst(astContext.RelatedAsts.Last());192 193            ExecutionContext executionContext = typeInferenceContext.ExecutionContext;194 195            return new CompletionContext196            {197                Options = _options,198                CursorPosition = _cursorPosition,199                TokenAtCursor = astContext.TokenAtCursor,200                TokenBeforeCursor = astContext.TokenBeforeCursor,201                RelatedAsts = astContext.RelatedAsts,202                ReplacementIndex = astContext.ReplacementIndex,203                ExecutionContext = executionContext,204                TypeInferenceContext = typeInferenceContext,205                Helper = typeInferenceContext.Helper,206                CustomArgumentCompleters = executionContext.CustomArgumentCompleters,207                NativeArgumentCompleters = executionContext.NativeArgumentCompleters,208            };209        }210 211        private static Token InterstingTokenAtCursorOrDefault(IReadOnlyList<Token> tokens, IScriptPosition cursorPosition)212        {213            for (int i = tokens.Count - 1; i >= 0; --i)214            {215                Token token = tokens[i];216                if (IsCursorWithinOrJustAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token))217                {218                    return token;219                }220            }221 222            return null;223        }224 225        private static Token InterstingTokenBeforeCursorOrDefault(IReadOnlyList<Token> tokens, IScriptPosition cursorPosition)226        {227            for (int i = tokens.Count - 1; i >= 0; --i)228            {229                Token token = tokens[i];230                if (IsCursorAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token))231                {232                    return token;233                }234            }235 236            return null;237        }238 239        private static Ast GetLastAstAtCursor(ScriptBlockAst scriptBlockAst, IScriptPosition cursorPosition)240        {241            var asts = AstSearcher.FindAll(scriptBlockAst, ast => IsCursorRightAfterExtent(cursorPosition, ast.Extent), searchNestedScriptBlocks: true);242            return asts.LastOrDefault();243        }244 245        #region Special Cases246 247        /// <summary>248        /// Check if we should complete file names for "switch -file"249        /// </summary>250        private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCursor)251        {252            Tuple<Token, Ast> fileConditionTuple;253 254            if (lastAst is ErrorStatementAst errorStatement && errorStatement.Flags is not null && errorStatement.Kind is not null && tokenBeforeCursor is not null &&255                errorStatement.Kind.Kind.Equals(TokenKind.Switch) && errorStatement.Flags.TryGetValue("file", out fileConditionTuple))256            {257                // Handle "switch -file <tab>"258                return fileConditionTuple.Item1.Extent.EndOffset == tokenBeforeCursor.Extent.EndOffset;259            }260 261            if (lastAst.Parent is CommandExpressionAst)262            {263                // Handle "switch -file m<tab>" or "switch -file *.ps1<tab>"264                if (lastAst.Parent.Parent is not PipelineAst pipeline)265                {266                    return false;267                }268 269                if (pipeline.Parent is not ErrorStatementAst parentErrorStatement || parentErrorStatement.Kind is null || parentErrorStatement.Flags is null)270                {271                    return false;272                }273 274                return (parentErrorStatement.Kind.Kind.Equals(TokenKind.Switch) &&275                        parentErrorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline);276            }277 278            return false;279        }280 281        /// <summary>282        /// Check if we should complete parameter names for switch cases on $PSBoundParameters.Keys283        /// </summary>284        private static List<CompletionResult> CompleteAgainstSwitchCaseCondition(CompletionContext completionContext)285        {286            var lastAst = completionContext.RelatedAsts.Last();287 288            PipelineAst conditionPipeline = null;289            Ast switchAst = null;290 291            // Check if we're in a switch statement (complete) or error statement (incomplete switch)292            if (lastAst.Parent is SwitchStatementAst switchStatementAst)293            {294                // Verify that the lastAst is one of the clause conditions (not in the body)295                bool isClauseCondition = switchStatementAst.Clauses.Any(clause => clause.Item1 == lastAst);296 297                if (!isClauseCondition)298                {299                    return null;300                }301 302                conditionPipeline = switchStatementAst.Condition as PipelineAst;303                switchAst = switchStatementAst;304            }305            else306            {307                // Check for incomplete switch parsed as ErrorStatementAst308                if (lastAst.Parent is not ErrorStatementAst errorStatementAst || errorStatementAst.Kind is null ||309                    errorStatementAst.Kind.Kind != TokenKind.Switch)310                {311                    return null;312                }313 314                // For ErrorStatementAst, the case value is in Bodies, condition is in Conditions315                bool isInBodies = errorStatementAst.Bodies != null && errorStatementAst.Bodies.Any(body => body == lastAst);316 317                if (!isInBodies)318                {319                    return null;320                }321 322                // Get the condition from ErrorStatementAst.Conditions323                if (errorStatementAst.Conditions != null && errorStatementAst.Conditions.Count > 0)324                {325                    conditionPipeline = errorStatementAst.Conditions[0] as PipelineAst;326                }327                switchAst = errorStatementAst;328            }329 330            if (conditionPipeline == null || conditionPipeline.PipelineElements.Count != 1)331            {332                return null;333            }334 335            if (conditionPipeline.PipelineElements[0] is not CommandExpressionAst commandExpressionAst)336            {337                return null;338            }339 340            // Check if the expression is a member access on $PSBoundParameters.Keys341            if (commandExpressionAst.Expression is not MemberExpressionAst memberExpressionAst)342            {343                return null;344            }345 346            // Check if the target is $PSBoundParameters347            if (memberExpressionAst.Expression is not VariableExpressionAst variableExpressionAst ||348                !variableExpressionAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase))349            {350                return null;351            }352 353            // Check if the member is "Keys"354            if (memberExpressionAst.Member is not StringConstantExpressionAst memberNameAst ||355                !memberNameAst.Value.Equals("Keys", StringComparison.OrdinalIgnoreCase))356            {357                return null;358            }359 360            // Find the nearest param block by traversing up the AST361            var paramBlockAst = FindNearestParamBlock(switchAst.Parent);362 363            if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0)364            {365                return null;366            }367 368            // Generate completion results from parameter names369            var wordToComplete = completionContext.WordToComplete ?? string.Empty;370            return CreateParameterCompletionResults(paramBlockAst, wordToComplete);371        }372 373        /// <summary>374        /// Check if we should complete parameter names for $PSBoundParameters access patterns375        /// Supports: $PSBoundParameters.ContainsKey('...'), $PSBoundParameters['...'], $PSBoundParameters.Remove('...')376        /// </summary>377        private static List<CompletionResult> CompleteAgainstPSBoundParametersAccess(CompletionContext completionContext)378        {379            var lastAst = completionContext.RelatedAsts.Last();380 381            // Must be a string constant382            if (lastAst is not StringConstantExpressionAst stringAst)383            {384                return null;385            }386 387            ExpressionAst targetAst = null;388 389            // Check for method invocation: $PSBoundParameters.ContainsKey('...') or $PSBoundParameters.Remove('...')390            if (lastAst.Parent is InvokeMemberExpressionAst invokeMemberAst)391            {392                if (invokeMemberAst.Member is StringConstantExpressionAst memberName &&393                    (memberName.Value.Equals("ContainsKey", StringComparison.OrdinalIgnoreCase) ||394                     memberName.Value.Equals("Remove", StringComparison.OrdinalIgnoreCase)))395                {396                    targetAst = invokeMemberAst.Expression;397                }398            }399            // Check for indexer: $PSBoundParameters['...']400            else if (lastAst.Parent is IndexExpressionAst indexAst)401            {402                targetAst = indexAst.Target;403            }404 405            if (targetAst is null)406            {407                return null;408            }409 410            // Check if target is $PSBoundParameters411            if (targetAst is not VariableExpressionAst variableAst ||412                !variableAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase))413            {414                return null;415            }416 417            // Find the nearest param block418            var paramBlockAst = FindNearestParamBlock(lastAst.Parent);419 420            if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0)421            {422                return null;423            }424 425            // Generate completion results from parameter names426            var wordToComplete = completionContext.WordToComplete ?? string.Empty;427 428            // Determine quote style based on the string constant type429            string quoteChar = string.Empty;430            if (stringAst.StringConstantType == StringConstantType.SingleQuoted)431            {432                quoteChar = "'";433            }434            else if (stringAst.StringConstantType == StringConstantType.DoubleQuoted)435            {436                quoteChar = "\"";437            }438 439            return CreateParameterCompletionResults(paramBlockAst, wordToComplete, quoteChar);440        }441 442        /// <summary>443        /// Finds the nearest ParamBlockAst by traversing up the AST hierarchy.444        /// </summary>445        /// <param name="startAst">The AST node to start searching from.</param>446        /// <returns>The nearest ParamBlockAst if found; otherwise, null.</returns>447        private static ParamBlockAst FindNearestParamBlock(Ast startAst)448        {449            Ast current = startAst;450            while (current != null)451            {452                if (current is FunctionDefinitionAst functionDefinitionAst)453                {454                    return functionDefinitionAst.Body?.ParamBlock;455                }456                else if (current is ScriptBlockAst scriptBlockAst)457                {458                    var paramBlock = scriptBlockAst.ParamBlock;459                    if (paramBlock != null)460                    {461                        return paramBlock;462                    }463                }464 465                current = current.Parent;466            }467 468            return null;469        }470 471        /// <summary>472        /// Creates completion results from parameter names with optional quote wrapping.473        /// </summary>474        /// <param name="paramBlockAst">The parameter block containing parameters to complete.</param>475        /// <param name="wordToComplete">The partial word to match against parameter names.</param>476        /// <param name="quoteChar">Optional quote character to wrap completion text (empty string for no quotes).</param>477        /// <returns>A list of completion results, or null if no matches found.</returns>478        private static List<CompletionResult> CreateParameterCompletionResults(479            ParamBlockAst paramBlockAst,480            string wordToComplete,481            string quoteChar = "")482        {483            var result = paramBlockAst.Parameters484                .Select(parameter => parameter.Name.VariablePath.UserPath)485                .Where(parameterName => parameterName.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))486                .Select(parameterName =>487                    new CompletionResult(488                        quoteChar + parameterName + quoteChar,489                        parameterName,490                        CompletionResultType.ParameterValue,491                        parameterName))492                .ToList();493 494            return result.Count > 0 ? result : null;495        }496 497        private static bool CompleteOperator(Token tokenAtCursor, Ast lastAst)498        {499            if (tokenAtCursor.Kind == TokenKind.Minus)500            {501                return lastAst is BinaryExpressionAst;502            }503            else if (tokenAtCursor.Kind == TokenKind.Parameter)504            {505                if (lastAst is CommandParameterAst)506                    return lastAst.Parent is ExpressionAst;507            }508 509            return false;510        }511 512        private static bool CompleteAgainstStatementFlags(Ast scriptAst, Ast lastAst, Token token, out TokenKind kind)513        {514            kind = TokenKind.Unknown;515 516            // Handle "switch -f<tab>"517            var errorStatement = lastAst as ErrorStatementAst;518            if (errorStatement != null && errorStatement.Kind != null)519            {520                switch (errorStatement.Kind.Kind)521                {522                    case TokenKind.Switch:523                        kind = TokenKind.Switch;524                        return true;525                    default:526                        break;527                }528            }529 530            // Handle "switch -<tab>". Skip cases like "switch ($a) {} -<tab> "531            var scriptBlockAst = scriptAst as ScriptBlockAst;532            if (token != null && token.Kind == TokenKind.Minus && scriptBlockAst != null)533            {534                var asts = AstSearcher.FindAll(scriptBlockAst, ast => IsCursorAfterExtent(token.Extent.StartScriptPosition, ast.Extent), searchNestedScriptBlocks: true);535 536                Ast last = asts.LastOrDefault();537                errorStatement = null;538 539                while (last != null)540                {541                    errorStatement = last as ErrorStatementAst;542                    if (errorStatement != null) { break; }543 544                    last = last.Parent;545                }546 547                if (errorStatement != null && errorStatement.Kind != null)548                {549                    switch (errorStatement.Kind.Kind)550                    {551                        case TokenKind.Switch:552 553                            Tuple<Token, Ast> value;554                            if (errorStatement.Flags != null && errorStatement.Flags.TryGetValue(Parser.VERBATIM_ARGUMENT, out value))555                            {556                                if (IsTokenTheSame(value.Item1, token))557                                {558                                    kind = TokenKind.Switch;559                                    return true;560                                }561                            }562 563                            break;564 565                        default:566                            break;567                    }568                }569            }570 571            return false;572        }573 574        private static bool IsTokenTheSame(Token x, Token y)575        {576            if (x.Kind == y.Kind && x.TokenFlags == y.TokenFlags &&577                x.Extent.StartLineNumber == y.Extent.StartLineNumber &&578                x.Extent.StartColumnNumber == y.Extent.StartColumnNumber &&579                x.Extent.EndLineNumber == y.Extent.EndLineNumber &&580                x.Extent.EndColumnNumber == y.Extent.EndColumnNumber)581            {582                return true;583            }584 585            return false;586        }587 588        #endregion Special Cases589 590        internal List<CompletionResult> GetResults(PowerShell powerShell, out int replacementIndex, out int replacementLength)591        {592            var completionContext = CreateCompletionContext(powerShell);593 594            PSLanguageMode? previousLanguageMode = null;595            try596            {597                // Tab expansion is called from a trusted function - we should apply ConstrainedLanguage if necessary.598                if (completionContext.ExecutionContext.HasRunspaceEverUsedConstrainedLanguageMode)599                {600                    previousLanguageMode = completionContext.ExecutionContext.LanguageMode;601                    completionContext.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage;602                }603 604                return GetResultHelper(completionContext, out replacementIndex, out replacementLength);605            }606            finally607            {608                if (previousLanguageMode.HasValue)609                {610                    completionContext.ExecutionContext.LanguageMode = previousLanguageMode.Value;611                }612            }613        }614 615        internal List<CompletionResult> GetResultHelper(CompletionContext completionContext, out int replacementIndex, out int replacementLength)616        {617            replacementIndex = -1;618            replacementLength = -1;619 620            var tokenAtCursor = completionContext.TokenAtCursor;621            var lastAst = completionContext.RelatedAsts.Last();622            List<CompletionResult> result = null;623            if (tokenAtCursor != null)624            {625                replacementIndex = tokenAtCursor.Extent.StartScriptPosition.Offset;626                replacementLength = tokenAtCursor.Extent.EndScriptPosition.Offset - replacementIndex;627 628                completionContext.ReplacementIndex = replacementIndex;629                completionContext.ReplacementLength = replacementLength;630 631                switch (tokenAtCursor.Kind)632                {633                    case TokenKind.Variable:634                    case TokenKind.SplattedVariable:635                        completionContext.WordToComplete = ((VariableToken)tokenAtCursor).VariablePath.UserPath;636                        result = CompletionCompleters.CompleteVariable(completionContext);637                        break;638 639                    case TokenKind.Multiply:640                    case TokenKind.Generic:641                    case TokenKind.MinusMinus: // for native commands '--'642                    case TokenKind.Identifier:643                        if (!tokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName))644                        {645                            result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength);646                            if (result is not null)647                            {648                                return result;649                            }650 651                            result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength);652                        }653 654                        break;655 656                    case TokenKind.Parameter:657                        completionContext.WordToComplete = tokenAtCursor.Text;658                        var cmdAst = lastAst.Parent as CommandAst;659                        if (lastAst is StringConstantExpressionAst && cmdAst != null && cmdAst.CommandElements.Count == 1)660                        {661                            result = CompleteFileNameAsCommand(completionContext);662                            break;663                        }664 665                        TokenKind statementKind;666                        if (CompleteAgainstStatementFlags(null, lastAst, null, out statementKind))667                        {668                            result = CompletionCompleters.CompleteStatementFlags(statementKind, completionContext.WordToComplete);669                            break;670                        }671 672                        if (CompleteOperator(tokenAtCursor, lastAst))673                        {674                            result = CompletionCompleters.CompleteOperator(completionContext.WordToComplete);675                            break;676                        }677 678                        // Handle scenarios like this: dir -path:<tab>679                        if (completionContext.WordToComplete.EndsWith(':'))680                        {681                            replacementIndex = tokenAtCursor.Extent.EndScriptPosition.Offset;682                            replacementLength = 0;683 684                            completionContext.WordToComplete = string.Empty;685                            result = CompletionCompleters.CompleteCommandArgument(completionContext);686                        }687                        else688                        {689                            result = CompletionCompleters.CompleteCommandParameter(completionContext);690                        }691 692                        break;693 694                    case TokenKind.Dot:695                    case TokenKind.ColonColon:696                    case TokenKind.QuestionDot:697                        replacementIndex += tokenAtCursor.Text.Length;698                        replacementLength = 0;699                        result = CompletionCompleters.CompleteMember(completionContext, @static: tokenAtCursor.Kind == TokenKind.ColonColon, ref replacementLength);700 701                        break;702 703                    case TokenKind.Comment:704                        completionContext.WordToComplete = tokenAtCursor.Text;705                        result = CompletionCompleters.CompleteComment(completionContext, ref replacementIndex, ref replacementLength);706                        break;707 708                    case TokenKind.StringExpandable:709                    case TokenKind.StringLiteral:710                        // Search to see if we're looking at an assignment711                        if (lastAst.Parent is CommandExpressionAst712                            && lastAst.Parent.Parent is AssignmentStatementAst assignmentAst)713                        {714                            // Handle scenarios like `$ErrorActionPreference = '<tab>`715                            if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst, out List<CompletionResult> completions))716                            {717                                return completions;718                            }719                        }720                        else if (lastAst.Parent is BinaryExpressionAst binaryExpression)721                        {722                            completionContext.WordToComplete = (tokenAtCursor as StringToken).Value;723                            result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left);724                            if (result.Count > 0)725                            {726                                return result;727                            }728                        }729                        else if (lastAst.Parent is IndexExpressionAst indexExpressionAst)730                        {731                            // Handles quoted string inside index expression like: $PSVersionTable["<Tab>"]732                            completionContext.WordToComplete = (tokenAtCursor as StringToken).Value;733                            // Check for $PSBoundParameters indexer first734                            var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext);735                            if (psBoundResult != null && psBoundResult.Count > 0)736                            {737                                return psBoundResult;738                            }739 740                            return CompletionCompleters.CompleteIndexExpression(completionContext, indexExpressionAst.Target);741                        }742 743                        result = GetResultForString(completionContext, ref replacementIndex, ref replacementLength);744                        break;745 746                    case TokenKind.RBracket:747                        if (lastAst is TypeExpressionAst)748                        {749                            var targetExpr = (TypeExpressionAst)lastAst;750                            var memberResult = new List<CompletionResult>();751 752                            CompletionCompleters.CompleteMemberHelper(753                                true,754                                "*",755                                targetExpr,756                                completionContext, memberResult);757 758                            if (memberResult.Count > 0)759                            {760                                replacementIndex++;761                                replacementLength = 0;762                                result = (from entry in memberResult763                                          let completionText = TokenKind.ColonColon.Text() + entry.CompletionText764                                          select new CompletionResult(completionText, entry.ListItemText, entry.ResultType, entry.ToolTip)).ToList();765                            }766                        }767 768                        break;769 770                    case TokenKind.Comma:771                        // Handle array elements such as the followings:772                        //  - `dir .\cd,<tab>`773                        //  - `dir -Path: .\cd,<tab>`774                        //  - `dir .\abc.txt,<tab> -File`775                        //  - `dir -Path .\abc.txt,<tab> -File`776                        //  - `dir -Path: .\abc.txt,<tab> -File`777                        if (lastAst is ErrorExpressionAst or ArrayLiteralAst &&778                            lastAst.Parent is CommandAst or CommandParameterAst)779                        {780                            replacementIndex += replacementLength;781                            replacementLength = 0;782 783                            result = CompletionCompleters.CompleteCommandArgument(completionContext);784                        }785                        else if (lastAst is AttributeAst)786                        {787                            completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length;788                            completionContext.ReplacementLength = replacementLength = 0;789                            result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);790                        }791                        else792                        {793                            //794                            // Handle auto completion for enum/dependson property of DSC resource,795                            // cursor is right after ','796                            //797                            // Configuration config798                            // {799                            //     User test800                            //     {801                            //         DependsOn=@('[user]x',|)802                            //803                            result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _);804                        }805 806                        break;807                    case TokenKind.AtCurly:808                        // Handle scenarios such as 'Sort-Object @{<tab>' and  'gci | Format-Table @{'809                        result = GetResultForHashtable(completionContext);810                        replacementIndex += 2;811                        replacementLength = 0;812                        break;813 814                    case TokenKind.Semi:815                        // Handle scenarios such as 'gci | Format-Table @{Label=...;<tab>'816                        if (lastAst is HashtableAst)817                        {818                            result = GetResultForHashtable(completionContext);819                            replacementIndex += 1;820                            replacementLength = 0;821                        }822 823                        break;824 825                    case TokenKind.Number:826                        // Handle scenarios such as Get-Process -Id 5<tab> || Get-Process -Id 5210, 3<tab> || Get-Process -Id: 5210, 3<tab>827                        if (lastAst is ConstantExpressionAst &&828                            (lastAst.Parent is CommandAst || lastAst.Parent is CommandParameterAst ||829                            (lastAst.Parent is ArrayLiteralAst &&830                             (lastAst.Parent.Parent is CommandAst || lastAst.Parent.Parent is CommandParameterAst))))831                        {832                            completionContext.WordToComplete = tokenAtCursor.Text;833                            result = CompletionCompleters.CompleteCommandArgument(completionContext);834 835                            replacementIndex = completionContext.ReplacementIndex;836                            replacementLength = completionContext.ReplacementLength;837                        }838                        else if (lastAst.Parent is CommandExpressionAst839                            && lastAst.Parent.Parent is AssignmentStatementAst assignmentAst2)840                        {841                            // Handle scenarios like '[ValidateSet(11,22)][int]$i = 11; $i = 2<tab>'842                            if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst2, out List<CompletionResult> completions))843                            {844                                result = completions;845                            }846                        }847 848                        break;849 850                    case TokenKind.Redirection:851                        // Handle file name completion after the redirection operator: gps ><tab> || gps >><tab> || dir con 2><tab> || dir con 2>><tab>852                        if (lastAst is ErrorExpressionAst && lastAst.Parent is FileRedirectionAst)853                        {854                            completionContext.WordToComplete = string.Empty;855                            completionContext.ReplacementIndex = (replacementIndex += tokenAtCursor.Text.Length);856                            completionContext.ReplacementLength = replacementLength = 0;857                            result = new List<CompletionResult>(CompletionCompleters.CompleteFilename(completionContext));858                        }859 860                        break;861 862                    case TokenKind.Minus:863                        // Handle operator completion: 55 -<tab> || "string" -<tab> || (Get-Something) -<tab>864                        if (CompleteOperator(tokenAtCursor, lastAst))865                        {866                            result = CompletionCompleters.CompleteOperator(string.Empty);867                            break;868                        }869 870                        // Handle the flag completion for statements, such as the switch statement871                        if (CompleteAgainstStatementFlags(completionContext.RelatedAsts[0], null, tokenAtCursor, out statementKind))872                        {873                            completionContext.WordToComplete = tokenAtCursor.Text;874                            result = CompletionCompleters.CompleteStatementFlags(statementKind, completionContext.WordToComplete);875                            break;876                        }877 878                        break;879 880                    case TokenKind.DynamicKeyword:881                        {882                            DynamicKeywordStatementAst keywordAst;883                            ConfigurationDefinitionAst configureAst = GetAncestorConfigurationAstAndKeywordAst(884                                completionContext.CursorPosition, lastAst, out keywordAst);885                            Diagnostics.Assert(configureAst != null, "ConfigurationDefinitionAst should never be null");886                            bool matched = false;887                            completionContext.WordToComplete = tokenAtCursor.Text.Trim();888                            // Current token is within ConfigurationDefinitionAst or DynamicKeywordStatementAst889                            return GetResultForIdentifierInConfiguration(completionContext, configureAst, null, out matched);890                        }891                    case TokenKind.Equals:892                    case TokenKind.AtParen:893                    case TokenKind.LParen:894                        {895                            if (lastAst is AttributeAst)896                            {897                                completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length;898                                completionContext.ReplacementLength = replacementLength = 0;899                                result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);900                            }901                            else if (lastAst is HashtableAst hashTableAst && lastAst.Parent is not DynamicKeywordStatementAst && CheckForPendingAssignment(hashTableAst))902                            {903                                // Handle scenarios such as 'gci | Format-Table @{Label=<tab>' if incomplete parsing of the assignment.904                                return null;905                            }906                            else if (lastAst is AssignmentStatementAst assignmentAst2)907                            {908                                completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length;909                                completionContext.ReplacementLength = replacementLength = 0;910 911                                // Handle scenarios like '$ErrorActionPreference =<tab>'912                                if (TryGetCompletionsForVariableAssignment(completionContext, assignmentAst2, out List<CompletionResult> completions))913                                {914                                    return completions;915                                }916                            }917                            else if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0)918                            {919                                foreach (AttributeBaseAst attribute in paramAst.Attributes)920                                {921                                    if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent))922                                    {923                                        completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length;924                                        completionContext.ReplacementLength = replacementLength = 0;925                                        result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);926                                        break;927                                    }928                                }929                            }930                            else931                            {932                                // Handle scenarios such as 'configuration foo { File ab { Attributes ='933                                // (auto completion for enum/dependson property of DSC resource),934                                // cursor is right after '=', '(' or '@('935                                //936                                // Configuration config937                                // {938                                //     User test939                                //     {940                                //         DependsOn=|941                                //         DependsOn=@(|)942                                //         DependsOn=(|943                                //944                                result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _);945                            }946 947                            break;948                        }949 950                    case TokenKind.Format:951                    case TokenKind.Not:952                    case TokenKind.Bnot:953                    case TokenKind.And:954                    case TokenKind.Or:955                    case TokenKind.Xor:956                    case TokenKind.Band:957                    case TokenKind.Bor:958                    case TokenKind.Bxor:959                    case TokenKind.Join:960                    case TokenKind.Ieq:961                    case TokenKind.Ine:962                    case TokenKind.Ige:963                    case TokenKind.Igt:964                    case TokenKind.Ilt:965                    case TokenKind.Ile:966                    case TokenKind.Ilike:967                    case TokenKind.Inotlike:968                    case TokenKind.Imatch:969                    case TokenKind.Inotmatch:970                    case TokenKind.Ireplace:971                    case TokenKind.Icontains:972                    case TokenKind.Inotcontains:973                    case TokenKind.Iin:974                    case TokenKind.Inotin:975                    case TokenKind.Isplit:976                    case TokenKind.Ceq:977                    case TokenKind.Cne:978                    case TokenKind.Cge:979                    case TokenKind.Cgt:980                    case TokenKind.Clt:981                    case TokenKind.Cle:982                    case TokenKind.Clike:983                    case TokenKind.Cnotlike:984                    case TokenKind.Cmatch:985                    case TokenKind.Cnotmatch:986                    case TokenKind.Creplace:987                    case TokenKind.Ccontains:988                    case TokenKind.Cnotcontains:989                    case TokenKind.Cin:990                    case TokenKind.Cnotin:991                    case TokenKind.Csplit:992                    case TokenKind.Is:993                    case TokenKind.IsNot:994                    case TokenKind.As:995                    case TokenKind.Shl:996                    case TokenKind.Shr:997                        result = CompletionCompleters.CompleteOperator(tokenAtCursor.Text);998                        break;999 1000                    case TokenKind.LBracket:1001                        if (lastAst.Parent is IndexExpressionAst indexExpression)1002                        {1003                            // Handles index expression with cursor right after lbracket like: $PSVersionTable[<Tab>]1004                            completionContext.WordToComplete = string.Empty;1005                            result = CompletionCompleters.CompleteIndexExpression(completionContext, indexExpression.Target);1006                            if (result.Count > 0)1007                            {1008                                replacementIndex++;1009                                replacementLength--;1010                            }1011                        }1012                        break;1013                    default:1014                        result = CompleteUsingKeywords(completionContext.CursorPosition.Offset, _tokens, ref replacementIndex, ref replacementLength);1015                        if (result is not null)1016                        {1017                            return result;1018                        }1019 1020                        if ((tokenAtCursor.TokenFlags & TokenFlags.Keyword) != 0)1021                        {1022                            completionContext.WordToComplete = tokenAtCursor.Text;1023 1024                            // Handle the file name completion1025                            result = CompleteFileNameAsCommand(completionContext);1026 1027                            // Handle the command name completion1028                            var commandNameResult = CompletionCompleters.CompleteCommand(completionContext);1029                            if (commandNameResult != null && commandNameResult.Count > 0)1030                            {1031                                result.AddRange(commandNameResult);1032                            }1033                        }1034                        else1035                        {1036                            replacementIndex = -1;1037                            replacementLength = -1;1038                        }1039 1040                        break;1041                }1042            }1043            else1044            {1045                IScriptPosition cursor = completionContext.CursorPosition;1046                bool isCursorLineEmpty = string.IsNullOrWhiteSpace(cursor.Line);1047                var tokenBeforeCursor = completionContext.TokenBeforeCursor;1048                bool isLineContinuationBeforeCursor = false;1049                if (tokenBeforeCursor != null)1050                {1051                    //1052                    // Handle following scenario, cursor is in next line and after a command call,1053                    // we need to skip the command call autocompletion if there is no backtick character1054                    // in the end of the previous line, since backtick means command call continues to the next line1055                    //1056                    // Configuration config1057                    // {1058                    //     User test1059                    //     {1060                    //         DependsOn=zzz1061                    //         |1062                    //1063                    isLineContinuationBeforeCursor = completionContext.TokenBeforeCursor.Kind == TokenKind.LineContinuation;1064                }1065 1066                bool skipAutoCompleteForCommandCall = isCursorLineEmpty && !isLineContinuationBeforeCursor;1067                bool lastAstIsExpressionAst = lastAst is ExpressionAst;1068                if (!skipAutoCompleteForCommandCall &&1069                    (lastAst is CommandParameterAst || lastAst is CommandAst ||1070                    (lastAstIsExpressionAst && lastAst.Parent is CommandAst) ||1071                    (lastAstIsExpressionAst && lastAst.Parent is CommandParameterAst) ||1072                    (lastAstIsExpressionAst && lastAst.Parent is ArrayLiteralAst &&1073                    (lastAst.Parent.Parent is CommandAst || lastAst.Parent.Parent is CommandParameterAst))))1074                {1075                    completionContext.WordToComplete = string.Empty;1076 1077                    var hashTableAst = lastAst as HashtableAst;1078 1079                    // Do not do any tab completion if we have a hash table1080                    // and an assignment is pending.  For cases like:1081                    //   new-object System.Drawing.Point -prop @{ X=  -> Tab should not complete1082                    // Note: This check works when all statements preceding the last are complete,1083                    //       but if a preceding statement is incomplete this test fails because1084                    //       the Ast mixes the statements due to incomplete parsing.1085                    //   e.g.,1086                    //   new-object System.Drawing.Point -prop @{ X = 100; Y =      <- Incomplete line1087                    //   new-object new-object System.Drawing.Point -prop @{ X =    <- Tab will yield hash properties.1088                    if (hashTableAst != null &&1089                        CheckForPendingAssignment(hashTableAst))1090                    {1091                        return result;1092                    }1093 1094                    if (hashTableAst != null)1095                    {1096                        completionContext.ReplacementIndex = replacementIndex = completionContext.CursorPosition.Offset;1097                        completionContext.ReplacementLength = replacementLength = 0;1098                        result = CompletionCompleters.CompleteHashtableKey(completionContext, hashTableAst);1099                    }1100                    else1101                    {1102                        result = CompletionCompleters.CompleteCommandArgument(completionContext);1103                        replacementIndex = completionContext.ReplacementIndex;1104                        replacementLength = completionContext.ReplacementLength;1105                    }1106                }1107                else1108                {1109                    //1110                    // Handle completion of empty line within configuration statement1111                    // Ignore the auto completion if there is a backtick character in previous line1112                    //1113                    bool cursorAtLineContinuation;1114                    if ((tokenAtCursor != null && tokenAtCursor.Kind == TokenKind.LineContinuation) ||1115                        (tokenBeforeCursor != null && tokenBeforeCursor.Kind == TokenKind.LineContinuation))1116                        cursorAtLineContinuation = true;1117                    else1118                        cursorAtLineContinuation = false;1119                    if (isCursorLineEmpty && !cursorAtLineContinuation)1120                    {1121                        //1122                        // Handle following scenario, both Configuration and DSC resource 'User' are not complete1123                        // Check Hashtable first, and then fallback to configuration1124                        //1125                        // Configuration config1126                        // {1127                        //     User test1128                        //     {1129                        //         DependsOn=''1130                        //         |1131                        result = GetResultForHashtable(completionContext);1132                        if (result == null || result.Count == 0)1133                        {1134                            DynamicKeywordStatementAst keywordAst;1135                            ConfigurationDefinitionAst configAst = GetAncestorConfigurationAstAndKeywordAst(cursor, lastAst, out keywordAst);1136                            if (configAst != null)1137                            {1138                                bool matched;1139                                result = GetResultForIdentifierInConfiguration(completionContext, configAst, keywordAst, out matched);1140                            }1141                        }1142 1143                        // Handles following scenario where user is tab completing a member on an empty line:1144                        // "Hello".1145                        // <Tab>1146                        if ((result is null || result.Count == 0) && tokenBeforeCursor is not null)1147                        {1148                            switch (completionContext.TokenBeforeCursor.Kind)1149                            {1150 1151                                case TokenKind.Dot:1152                                case TokenKind.ColonColon:1153                                case TokenKind.QuestionDot:1154                                    replacementIndex = cursor.Offset;1155                                    replacementLength = 0;1156                                    result = CompletionCompleters.CompleteMember(completionContext, @static: completionContext.TokenBeforeCursor.Kind == TokenKind.ColonColon, ref replacementLength);1157                                    break;1158 1159                                case TokenKind.LParen:1160                                case TokenKind.Comma:1161                                    if (lastAst is AttributeAst)1162                                    {1163                                        result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);1164                                    }1165 1166                                    if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0)1167                                    {1168                                        foreach (AttributeBaseAst attribute in paramAst.Attributes)1169                                        {1170                                            if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent))1171                                            {1172                                                result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength);1173                                                break;1174                                            }1175                                        }1176                                    }1177                                    break;1178 1179                                case TokenKind.Ieq:1180                                case TokenKind.Ceq:1181                                case TokenKind.Ine:1182                                case TokenKind.Cne:1183                                case TokenKind.Ilike:1184                                case TokenKind.Clike:1185                                case TokenKind.Inotlike:1186                                case TokenKind.Cnotlike:1187                                case TokenKind.Imatch:1188                                case TokenKind.Cmatch:1189                                case TokenKind.Inotmatch:1190                                case TokenKind.Cnotmatch:1191                                    if (lastAst is BinaryExpressionAst binaryExpression)1192                                    {1193                                        completionContext.WordToComplete = string.Empty;1194                                        result = CompletionCompleters.CompleteComparisonOperatorValues(completionContext, binaryExpression.Left);1195                                    }1196                                    break;1197 1198                                case TokenKind.LBracket:1199                                    if (lastAst.Parent is IndexExpressionAst indexExpression)1200                                    {

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