diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 59bd18005e..42a60ab7e0 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -693,6 +693,19 @@ namespace System.Management.Automation return completions; } } + else if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length; + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } else { // Handle scenarios such as 'configuration foo { File ab { Attributes =' @@ -928,6 +941,18 @@ namespace System.Management.Automation { result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); } + + if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } break; case TokenKind.Ieq: @@ -1003,6 +1028,21 @@ namespace System.Management.Automation break; } + if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + + break; + } + result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _); break; } @@ -2100,9 +2140,25 @@ namespace System.Management.Automation } } } - if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName && (lastAst is NamedAttributeArgumentAst || lastAst.Parent is NamedAttributeArgumentAst)) + + if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName) { - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + if (lastAst is NamedAttributeArgumentAst || lastAst.Parent is NamedAttributeArgumentAst) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + } + else if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(completionContext.CursorPosition, attribute.Extent)) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } + if (result is not null) { return result; diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 8664da5194..1a7b1adaca 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -7154,7 +7154,7 @@ namespace System.Management.Automation.Language ParserStrings.UnexpectedAttribute, lastAttribute.TypeName.FullName); - return new ErrorExpressionAst(ExtentOf(token, lastAttribute)); + return new ErrorExpressionAst(ExtentOf(token, lastAttribute), attributes); } expr = new AttributedExpressionAst(ExtentOf(lastAttribute, child), lastAttribute, child); diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 8567e87772..301fccc031 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -901,6 +901,36 @@ ConstructorTestClass(int i, bool b) $diffs | Should -BeNullOrEmpty } + It 'Should complete attribute argument in incomplete param block' { + $res = TabExpansion2 -inputScript 'param([ValidatePattern(' + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + + It 'Should complete attribute argument in incomplete param block on new line' { + $TestString = @' +param([ValidatePattern( +^)]) +'@ + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + + It 'Should complete attribute argument with partially written name in incomplete param block' { + $TestString = 'param([ValidatePattern(op^)]' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Options' + } + + It 'Should complete attribute argument for incomplete standalone attribute' { + $res = TabExpansion2 -inputScript '[ValidatePattern(' + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + It 'Should complete argument for second parameter' { $res = TabExpansion2 -inputScript 'Get-ChildItem -Path $HOME -ErrorAction ' $res.CompletionMatches[0].CompletionText | Should -BeExactly Break