diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index acdbe2233bc..a41a336b12b 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -7168,7 +7168,7 @@ internal static List CompleteHashtableKey(CompletionContext co switch (binding.CommandName) { case "Get-WinEvent": - return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "LogName", "ProviderName", "Path", "Keywords", "ID", "Level", + return GetSpecialHashTableKeyMembers(excludedKeys, wordToComplete, "LogName", "ProviderName", "Path", "Keywords", "ID", "Level", "StartTime", "EndTime", "UserID", "Data", "SuppressHashFilter"); } } @@ -7298,32 +7298,40 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand internal static string CombineVariableWithPartialPath(VariableExpressionAst variableAst, string extraText, ExecutionContext executionContext) { var varPath = variableAst.VariablePath; - if (varPath.IsVariable || varPath.DriveName.Equals("env", StringComparison.OrdinalIgnoreCase)) + if (!varPath.IsVariable && !varPath.DriveName.Equals("env", StringComparison.OrdinalIgnoreCase)) { - try - { - // We check the strict mode inside GetVariableValue - object value = VariableOps.GetVariableValue(varPath, executionContext, variableAst); - var strValue = (value == null) ? string.Empty : value as string; + return null; + } - if (strValue == null) - { - object baseObj = PSObject.Base(value); - if (baseObj is string || baseObj.GetType().IsPrimitive) - { - strValue = LanguagePrimitives.ConvertTo(value); - } - } + if (varPath.UnqualifiedPath.Equals(SpecialVariables.PSScriptRoot, StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(variableAst.Extent.File)) + { + return Path.GetDirectoryName(variableAst.Extent.File) + extraText; + } - if (strValue != null) + try + { + // We check the strict mode inside GetVariableValue + object value = VariableOps.GetVariableValue(varPath, executionContext, variableAst); + var strValue = (value == null) ? string.Empty : value as string; + + if (strValue == null) + { + object baseObj = PSObject.Base(value); + if (baseObj is string || baseObj?.GetType()?.IsPrimitive is true) { - return strValue + extraText; + strValue = LanguagePrimitives.ConvertTo(value); } } - catch (Exception) + + if (strValue != null) { + return strValue + extraText; } } + catch (Exception) + { + } return null; } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index f984e3b72c0..f83c0ea0e8d 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -822,6 +822,26 @@ switch ($x) $expected = ($expected | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" } + + It "PSScriptRoot path completion when AST extent has file identity" { + $scriptText = '"$PSScriptRoot\BugFix.Tests"' + $tokens = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput( + $scriptText, + $PSCommandPath, + [ref] $tokens, + [ref] $null) + + $cursorPosition = $scriptAst.Extent.StartScriptPosition. + GetType(). + GetMethod('CloneWithNewOffset', [System.Reflection.BindingFlags]'NonPublic, Instance'). + Invoke($scriptAst.Extent.StartScriptPosition, @($scriptText.Length - 1)) + + $res = TabExpansion2 -ast $scriptAst -tokens $tokens -positionOfCursor $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $expectedPath = Join-Path $PSScriptRoot -ChildPath BugFix.Tests.ps1 + $res.CompletionMatches[0].CompletionText | Should -Be "`"$expectedPath`"" + } } Context "Cmdlet name completion" {