Skip to content

Commit 33d4bf5

Browse files
authored
Fix completion of provider paths when a path returns itself instead of its children (PowerShell#24755)
It is a workaround for Get-ChildItem behavior.
1 parent 84edf16 commit 33d4bf5

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs

+7
Original file line numberDiff line numberDiff line change
@@ -4842,6 +4842,13 @@ private static List<CompletionResult> GetDefaultProviderResults(
48424842
bool hadErrors;
48434843
var childItemOutput = context.Helper.ExecuteCurrentPowerShell(out _, out hadErrors);
48444844

4845+
if (childItemOutput.Count == 1 &&
4846+
(pathInfo.Provider.FullName + "::" + pathInfo.ProviderPath).EqualsOrdinalIgnoreCase(childItemOutput[0].Properties["PSPath"].Value as string))
4847+
{
4848+
// Get-ChildItem returned the item itself instead of the children so there must be no child items to complete.
4849+
continue;
4850+
}
4851+
48454852
var childrenInfoTable = new Dictionary<string, bool>(childItemOutput.Count);
48464853
var childNameList = new List<string>(childItemOutput.Count);
48474854

test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1

+116
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,122 @@ Describe "TabCompletion" -Tags CI {
118118
}
119119
}
120120

121+
context CustomProviderTests {
122+
BeforeAll {
123+
$testModulePath = Join-Path $TestDrive "ReproModule"
124+
New-Item -Path $testModulePath -ItemType Directory > $null
125+
126+
New-ModuleManifest -Path "$testModulePath/ReproModule.psd1" -RootModule 'testmodule.dll'
127+
128+
$testBinaryModulePath = Join-Path $testModulePath "testmodule.dll"
129+
$binaryModule = @'
130+
using System;
131+
using System.Linq;
132+
using System.Management.Automation;
133+
using System.Management.Automation.Provider;
134+
135+
namespace BugRepro
136+
{
137+
public class IntItemInfo
138+
{
139+
public string Name;
140+
public IntItemInfo(string name) => Name = name;
141+
}
142+
143+
[CmdletProvider("Int", ProviderCapabilities.None)]
144+
public class IntProvider : NavigationCmdletProvider
145+
{
146+
public static string[] ToChunks(string path) => path.Split("/", StringSplitOptions.RemoveEmptyEntries);
147+
148+
protected string _ChildName(string path)
149+
{
150+
var name = ToChunks(path).LastOrDefault();
151+
return name ?? string.Empty;
152+
}
153+
154+
protected string Normalize(string path) => string.Join("/", ToChunks(path));
155+
156+
protected override string GetChildName(string path)
157+
{
158+
var name = _ChildName(path);
159+
// if (!IsItemContainer(path)) { return string.Empty; }
160+
return name;
161+
}
162+
163+
protected override bool IsValidPath(string path) => int.TryParse(GetChildName(path), out int _);
164+
165+
protected override bool IsItemContainer(string path)
166+
{
167+
var name = _ChildName(path);
168+
if (!int.TryParse(name, out int value))
169+
{
170+
return false;
171+
}
172+
if (ToChunks(path).Count() > 3)
173+
{
174+
return false;
175+
}
176+
return value % 2 == 0;
177+
}
178+
179+
protected override bool ItemExists(string path)
180+
{
181+
foreach (var chunk in ToChunks(path))
182+
{
183+
if (!int.TryParse(chunk, out int value))
184+
{
185+
return false;
186+
}
187+
if (value < 0 || value > 9)
188+
{
189+
return false;
190+
}
191+
}
192+
return true;
193+
}
194+
195+
protected override void GetItem(string path)
196+
{
197+
var name = GetChildName(path);
198+
if (!int.TryParse(name, out int _))
199+
{
200+
return;
201+
}
202+
WriteItemObject(new IntItemInfo(name), path, IsItemContainer(path));
203+
}
204+
protected override bool HasChildItems(string path) => IsItemContainer(path);
205+
206+
protected override void GetChildItems(string path, bool recurse)
207+
{
208+
if (!IsItemContainer(path)) { GetItem(path); return; }
209+
210+
for (var i = 0; i <= 9; i++)
211+
{
212+
var _path = $"{Normalize(path)}/{i}";
213+
if (recurse)
214+
{
215+
GetChildItems(_path, recurse);
216+
}
217+
else
218+
{
219+
GetItem(_path);
220+
}
221+
}
222+
}
223+
}
224+
}
225+
'@
226+
Add-Type -OutputAssembly $testBinaryModulePath -TypeDefinition $binaryModule
227+
228+
$pwsh = "$PSHOME\pwsh"
229+
}
230+
231+
It "Should not complete invalid items when a provider path returns itself instead of its children" {
232+
$result = & $pwsh -NoProfile -Command "Import-Module -Name $testModulePath; (TabExpansion2 'Get-ChildItem Int::/2/3/').CompletionMatches.Count"
233+
$result | Should -BeExactly "0"
234+
}
235+
}
236+
121237
It 'should complete index expression for <Intent>' -TestCases @(
122238
@{
123239
Intent = 'Hashtable with no user input'

0 commit comments

Comments
 (0)