diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
index 853b79e784..1d346e0259 100644
--- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
+++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
@@ -2030,19 +2030,17 @@ namespace System.Management.Automation
{
try
{
- if (argumentCompleterAttribute.Type != null)
+ var completer = argumentCompleterAttribute.CreateArgumentCompleter();
+
+ if (completer != null)
{
- var completer = Activator.CreateInstance(argumentCompleterAttribute.Type) as IArgumentCompleter;
- if (completer != null)
+ var customResults = completer.CompleteArgument(commandName, parameterName,
+ context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
+ if (customResults != null)
{
- var customResults = completer.CompleteArgument(commandName, parameterName,
- context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
- if (customResults != null)
- {
- result.AddRange(customResults);
- result.Add(CompletionResult.Null);
- return;
- }
+ result.AddRange(customResults);
+ result.Add(CompletionResult.Null);
+ return;
}
}
else
diff --git a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs
index 777fc6c16f..0978262c0e 100644
--- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs
+++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs
@@ -40,19 +40,40 @@ namespace System.Management.Automation
Type = type;
}
+ ///
+ /// Initializes a new instance of the class.
+ /// This constructor is used by derived attributes implementing .
+ ///
+ protected ArgumentCompleterAttribute()
+ {
+ if (this is not IArgumentCompleterFactory)
+ {
+ throw PSTraceSource.NewInvalidOperationException();
+ }
+ }
+
///
/// This constructor is used primarily via PowerShell scripts.
///
///
public ArgumentCompleterAttribute(ScriptBlock scriptBlock)
{
- if (scriptBlock == null)
+ if (scriptBlock is null)
{
throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock));
}
ScriptBlock = scriptBlock;
}
+
+ internal IArgumentCompleter CreateArgumentCompleter()
+ {
+ return Type != null
+ ? Activator.CreateInstance(Type) as IArgumentCompleter
+ : this is IArgumentCompleterFactory factory
+ ? factory.Create()
+ : null;
+ }
}
///
@@ -85,6 +106,67 @@ namespace System.Management.Automation
}
#nullable restore
+ ///
+ /// Creates a new argument completer.
+ ///
+ ///
+ /// If an attribute that derives from implements this interface,
+ /// it will be used to create the , thus giving a way to parameterize a completer.
+ /// The derived attribute can have properties or constructor arguments that are used when creating the completer.
+ ///
+ ///
+ /// This example shows the intended usage of to pass arguments to an argument completer.
+ ///
+ /// public class NumberCompleterAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory {
+ /// private readonly int _from;
+ /// private readonly int _to;
+ ///
+ /// public NumberCompleterAttribute(int from, int to){
+ /// _from = from;
+ /// _to = to;
+ /// }
+ ///
+ /// // use the attribute parameters to create a parameterized completer
+ /// IArgumentCompleter Create() => new NumberCompleter(_from, _to);
+ /// }
+ ///
+ /// class NumberCompleter : IArgumentCompleter {
+ /// private readonly int _from;
+ /// private readonly int _to;
+ ///
+ /// public NumberCompleter(int from, int to){
+ /// _from = from;
+ /// _to = to;
+ /// }
+ ///
+ /// IEnumerable{CompletionResult} CompleteArgument(string commandName, string parameterName, string wordToComplete,
+ /// CommandAst commandAst, IDictionary fakeBoundParameters) {
+ /// for(int i = _from; i < _to; i++) {
+ /// yield return new CompletionResult(i.ToString());
+ /// }
+ /// }
+ /// }
+ ///
+ ///
+ public interface IArgumentCompleterFactory
+ {
+ ///
+ /// Creates an instance of a class implementing the interface.
+ ///
+ /// An IArgumentCompleter instance.
+ IArgumentCompleter Create();
+ }
+
+ ///
+ /// Base class for parameterized argument completer attributes.
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public abstract class ArgumentCompleterFactoryAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory
+ {
+ ///
+ public abstract IArgumentCompleter Create();
+ }
+
///
///
[Cmdlet(VerbsLifecycle.Register, "ArgumentCompleter", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528576")]
diff --git a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
index 3c9edd4391..3514f38971 100644
--- a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
+++ b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
@@ -175,6 +175,77 @@ function TestFunction
)
}
+
+class NumberCompleter : IArgumentCompleter
+{
+
+ [int] $From
+ [int] $To
+ [int] $Step
+
+ NumberCompleter([int] $from, [int] $to, [int] $step)
+ {
+ if ($from -gt $to) {
+ throw [ArgumentOutOfRangeException]::new("from")
+ }
+ $this.From = $from
+ $this.To = $to
+ $this.Step = if($step -lt 1) { 1 } else { $step }
+ }
+
+ [IEnumerable[CompletionResult]] CompleteArgument(
+ [string] $CommandName,
+ [string] $parameterName,
+ [string] $wordToComplete,
+ [CommandAst] $commandAst,
+ [IDictionary] $fakeBoundParameters)
+ {
+ $resultList = [List[CompletionResult]]::new()
+ $local:to = $this.To
+ for ($i = $this.From; $i -le $to; $i += $this.Step) {
+ if ($i.ToString().StartsWith($wordToComplete, [System.StringComparison]::Ordinal)) {
+ $num = $i.ToString()
+ $resultList.Add([CompletionResult]::new($num, $num, "ParameterValue", $num))
+ }
+ }
+
+ return $resultList
+ }
+}
+
+class NumberCompletionAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory
+{
+ [int] $From
+ [int] $To
+ [int] $Step
+
+ NumberCompletionAttribute([int] $from, [int] $to)
+ {
+ $this.From = $from
+ $this.To = $to
+ $this.Step = 1
+ }
+
+ [IArgumentCompleter] Create() { return [NumberCompleter]::new($this.From, $this.To, $this.Step) }
+}
+
+function FactoryCompletionAdd {
+ param(
+ [NumberCompletion(0, 50, Step = 5)]
+ [int] $Number
+ )
+}
+
+Describe "Factory based extensible completion" -Tags "CI" {
+ @{
+ ExpectedResults = @(
+ @{CompletionText = "5"; ResultType = "ParameterValue" }
+ @{CompletionText = "50"; ResultType = "ParameterValue" }
+ )
+ TestInput = 'FactoryCompletionAdd -Number 5'
+ } | Get-CompletionTestCaseData | Test-Completions
+}
+
Describe "Script block based extensible completion" -Tags "CI" {
@{
ExpectedResults = @(