diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs index 5533721b5f6..d3205ba5e46 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs @@ -206,6 +206,7 @@ public int Millisecond /// Unix format string /// [Parameter(ParameterSetName = "net")] + [ArgumentCompletions("FileDate", "FileDateUniversal", "FileDateTime", "FileDateTimeUniversal")] public string Format { get; set; } #endregion diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 844a3588de9..4ce76a9a88d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -2002,6 +2002,20 @@ private static void NativeCommandArgumentCompletion( { } } + + var argumentCompletionsAttribute = parameter.CompiledAttributes.OfType().FirstOrDefault(); + if (argumentCompletionsAttribute != null) + { + var customResults = argumentCompletionsAttribute.CompleteArgument(commandName, parameterName, + context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context)); + if (customResults != null) + { + result.AddRange(customResults); + result.Add(CompletionResult.Null); + return; + } + } + switch (commandName) { case "Get-Command": diff --git a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs index 7b001d14463..18dce545a42 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs @@ -158,4 +158,56 @@ protected override void EndProcessing() } } } + + /// + /// This attribute is used to specify an argument completions for a parameter of a cmdlet or function + /// based on string array. + /// + /// [Parameter()] + /// [ArgumentCompletions("Option1","Option2","Option3")] + /// public string Noun { get; set; } + /// + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ArgumentCompletionsAttribute : Attribute + { + private string[] _completions; + + /// + /// Initializes a new instance of the ArgumentCompletionsAttribute class + /// + /// list of complete values + /// for null arguments + /// for invalid arguments + public ArgumentCompletionsAttribute(params string[] completions) + { + if (completions == null) + { + throw PSTraceSource.NewArgumentNullException("completions"); + } + + if (completions.Length == 0) + { + throw PSTraceSource.NewArgumentOutOfRangeException("completions", completions); + } + + _completions = completions; + } + + /// + /// The function returns completions for arguments. + /// + public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + { + var wordToCompletePattern = WildcardPattern.Get(string.IsNullOrWhiteSpace(wordToComplete) ? "*" : wordToComplete + "*", WildcardOptions.IgnoreCase); + + foreach (var str in _completions) + { + if (wordToCompletePattern.IsMatch(str)) + { + yield return new CompletionResult(str, str, CompletionResultType.ParameterValue, str); + } + } + } + } } diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs index 0117d767e3f..006dbf084e5 100644 --- a/src/System.Management.Automation/engine/parser/TypeResolver.cs +++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs @@ -736,6 +736,7 @@ internal static class CoreTypes { typeof(AllowEmptyStringAttribute), new[] { "AllowEmptyString" } }, { typeof(AllowNullAttribute), new[] { "AllowNull" } }, { typeof(ArgumentCompleterAttribute), new[] { "ArgumentCompleter" } }, + { typeof(ArgumentCompletionsAttribute), new[] { "ArgumentCompletions" } }, { typeof(Array), new[] { "array" } }, { typeof(bool), new[] { "bool" } }, { typeof(byte), new[] { "byte" } }, diff --git a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 index 7082f91f352..61fc32b5201 100644 --- a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 +++ b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 @@ -355,3 +355,97 @@ Describe "Additional type name completion tests" -Tags "CI" { TestInput = 'Get-Command -ParameterType System.Collections.Generic.Dic' } | Get-CompletionTestCaseData | Test-Completions } + +Describe "ArgumentCompletionsAttribute tests" -Tags "CI" { + + BeforeAll { + function TestArgumentCompletionsAttribute + { + param( + [ArgumentCompletions("value1", "value2", "value3")] + $Alpha, + $Beta + ) + } + + function TestArgumentCompletionsAttribute1 + { + param( + [ArgumentCompletionsAttribute("value1", "value2", "value3")] + $Alpha, + $Beta + ) + } + + $cmdletSrc=@' + using System; + using System.Management.Automation; + using System.Collections.Generic; + + namespace Test.A { + + [Cmdlet(VerbsCommon.Get, "ArgumentCompletions")] + public class TestArgumentCompletionsAttributeCommand : PSCmdlet + { + [Parameter] + [ArgumentCompletions("value1", "value2", "value3")] + public string Param1; + + protected override void EndProcessing() + { + WriteObject(Param1); + } + } + + [Cmdlet(VerbsCommon.Get, "ArgumentCompletions1")] + public class TestArgumentCompletionsAttributeCommand1 : PSCmdlet + { + [Parameter] + [ArgumentCompletionsAttribute("value1", "value2", "value3")] + public string Param1; + + protected override void EndProcessing() + { + WriteObject(Param1); + } + } + } +'@ + $cls = Add-Type -TypeDefinition $cmdletSrc -PassThru | Select-Object -First 1 + $testModule = Import-Module $cls.Assembly -PassThru + + $testCasesScript = @( + @{ attributeName = "ArgumentCompletions" ; cmdletName = "TestArgumentCompletionsAttribute" }, + @{ attributeName = "ArgumentCompletionsAttribute"; cmdletName = "TestArgumentCompletionsAttribute1" } + ) + + $testCasesCSharp = @( + @{ attributeName = "ArgumentCompletions" ; cmdletName = "Get-ArgumentCompletions" }, + @{ attributeName = "ArgumentCompletionsAttribute"; cmdletName = "Get-ArgumentCompletions1" } + ) + } + + AfterAll { + Remove-Module -ModuleInfo $testModule + } + + It " works in script" -TestCases $testCasesScript { + param($attributeName, $cmdletName) + + $line = "$cmdletName -Alpha val" + $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 3 + $res.CompletionMatches.CompletionText -join " " | Should Be "value1 value2 value3" + { TestArgumentCompletionsAttribute -Alpha unExpectedValue } | Should Not Throw + } + + It " works in C#" -TestCases $testCasesCSharp { + param($attributeName, $cmdletName) + + $line = "$cmdletName -Param1 val" + $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 3 + $res.CompletionMatches.CompletionText -join " " | Should Be "value1 value2 value3" + { TestArgumentCompletionsAttribute -Param1 unExpectedValue } | Should Not Throw + } +} diff --git a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 index 479e7b4cec9..152650135dd 100644 --- a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 +++ b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 @@ -28,6 +28,10 @@ Describe "Type accelerators" -Tags "CI" { Accelerator = 'ArgumentCompleter' Type = [System.Management.Automation.ArgumentCompleterAttribute] } + @{ + Accelerator = 'ArgumentCompletions' + Type = [System.Management.Automation.ArgumentCompletionsAttribute] + } @{ Accelerator = 'array' Type = [System.Array] @@ -368,7 +372,7 @@ Describe "Type accelerators" -Tags "CI" { if ( $IsCoreCLR ) { - $totalAccelerators = 89 + $totalAccelerators = 90 } else { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Date.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Date.Tests.ps1 index fade58e6ece..f10ffadf41e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Date.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Date.Tests.ps1 @@ -81,34 +81,49 @@ Describe "Get-Date DRT Unit Tests" -Tags "CI" { Describe "Get-Date" -Tags "CI" { + It "-Format FileDate works" { + Get-date -Date 0030-01-01T01:02:03.0004 -Format FileDate | Should Be "00300101" + } + + It "-Format FileDateTime works" { + Get-date -Date 0030-01-01T01:02:03.0004 -Format FileDateTime | Should Be "00300101T0102030004" + } + + It "-Format FileDateTimeUniversal works" { + Get-date -Date 0030-01-01T01:02:03.0004z -Format FileDateTimeUniversal | Should Be "00300101T0102030004Z" + } + + It "-Format FileDateTimeUniversal works" { + Get-date -Date 0030-01-01T01:02:03.0004z -Format FileDateUniversal | Should Be "00300101Z" + } + It "Should have colons when ToString method is used" { - (Get-Date).ToString().Contains(":") | Should be $true - (Get-Date -DisplayHint Time).ToString().Contains(":") | Should be $true - (Get-Date -DisplayHint Date).ToString().Contains(":") | Should be $true + (Get-Date).ToString().Contains(":") | Should be $true + (Get-Date -DisplayHint Time).ToString().Contains(":") | Should be $true + (Get-Date -DisplayHint Date).ToString().Contains(":") | Should be $true } It "Should be able to use the format flag" { - # You would think that one could use simple loops here, but apparently powershell in Windows returns different values in loops - - (Get-Date -Format d).Contains("/") | Should be $true - (Get-Date -Format D).Contains(",") | Should be $true - (Get-Date -Format f).Contains(",") -and (Get-Date -Format f).Contains(":") | Should be $true - (Get-Date -Format F).Contains(",") -and (Get-Date -Format F).Contains(":") | Should be $true - (Get-Date -Format g).Contains("/") -and (Get-Date -Format g).Contains(":") | Should be $true - (Get-Date -Format G).Contains("/") -and (Get-Date -Format G).Contains(":") | Should be $true - (Get-Date -Format m).Contains(",") -or ` - (Get-Date -Format m).Contains(":") -or ` - (Get-Date -Format m).Contains("/") | Should be $false + # You would think that one could use simple loops here, but apparently powershell in Windows returns different values in loops + + (Get-Date -Format d).Contains("/") | Should be $true + (Get-Date -Format D).Contains(",") | Should be $true + (Get-Date -Format f).Contains(",") -and (Get-Date -Format f).Contains(":") | Should be $true + (Get-Date -Format F).Contains(",") -and (Get-Date -Format F).Contains(":") | Should be $true + (Get-Date -Format g).Contains("/") -and (Get-Date -Format g).Contains(":") | Should be $true + (Get-Date -Format G).Contains("/") -and (Get-Date -Format G).Contains(":") | Should be $true + (Get-Date -Format m).Contains(",") -or ` + (Get-Date -Format m).Contains(":") -or ` + (Get-Date -Format m).Contains("/") | Should be $false } It "Should check that Get-Date can return the correct datetime from the system time" { - $timeDifference = $(Get-Date).Subtract([System.DateTime]::Now) + $timeDifference = $(Get-Date).Subtract([System.DateTime]::Now) - $timeDifference.Days | Should Be 0 - $timeDifference.Hours | Should Be 0 - $timeDifference.Minutes | Should Be 0 - $timeDifference.Milliseconds | Should BeLessThan 1 - $timeDifference.Ticks | Should BeLessThan 10000 + $timeDifference.Days | Should Be 0 + $timeDifference.Hours | Should Be 0 + $timeDifference.Minutes | Should Be 0 + $timeDifference.Milliseconds | Should BeLessThan 1 + $timeDifference.Ticks | Should BeLessThan 10000 } - }