From 31405f7283e3ecd3e35ad2dca917908e95a749cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=A5hlin?= Date: Tue, 27 Mar 2018 01:34:13 +0200 Subject: [PATCH] Add the parameter '-Not' to 'Where-Object' (#6464) --- .../engine/InternalCommands.cs | 35 +++-- .../Where-Object.Tests.ps1 | 128 ++++++++++++++++++ 2 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 63e4c063f..ea04bee40 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -813,6 +813,7 @@ namespace Microsoft.PowerShell.Commands [Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotInSet")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsSet")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsNotSet")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Not")] [ValidateNotNullOrEmpty] public string Property { @@ -1199,6 +1200,16 @@ namespace Microsoft.PowerShell.Commands get { return _binaryOperator == TokenKind.IsNot; } } + /// + /// Binary operator -Not. + /// + [Parameter(Mandatory = true, ParameterSetName = "Not")] + public SwitchParameter Not + { + set { _binaryOperator = TokenKind.Not; } + get { return _binaryOperator == TokenKind.Not; } + } + #endregion binary operator parameters private readonly CallSite> _toBoolSite = @@ -1211,6 +1222,16 @@ namespace Microsoft.PowerShell.Commands return (x, y) => site.Target.Invoke(site, x, y); } + private static Func GetCallSiteDelegateBoolean(ExpressionType expressionType, bool ignoreCase) + { + // flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it + // equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to + // be compared under the bool context. So that '"string" | Where-Object Length' would behave + // just like '"string" | Where-Object {$_.Length}'. + var site = CallSite>.Create(binder: PSBinaryOperationBinder.Get(expressionType, ignoreCase)); + return (x, y) => site.Target.Invoke(site, y, x); + } + private static Tuple>, CallSite>> GetContainsCallSites(bool ignoreCase) { var enumerableSite = CallSite>.Create(PSEnumerableBinder.Get()); @@ -1261,12 +1282,7 @@ namespace Microsoft.PowerShell.Commands } else { - // flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it - // equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to - // be compared under the bool context. So that '"string" | Where-Object Length' would behave - // just like '"string" | Where-Object {$_.Length}'. - var site = CallSite>.Create(PSBinaryOperationBinder.Get(ExpressionType.Equal, true)); - _operationDelegate = (x, y) => site.Target.Invoke(site, y, x); + _operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.Equal, ignoreCase: true); } break; case TokenKind.Ceq: @@ -1338,6 +1354,9 @@ namespace Microsoft.PowerShell.Commands _operationDelegate = (lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: true, ignoreCase: false); break; + case TokenKind.Not: + _operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.NotEqual, ignoreCase: true); + break; // the second to last parameter in ContainsOperator has flipped semantics compared to others. // "true" means "contains" while "false" means "notcontains" case TokenKind.Icontains: @@ -1463,7 +1482,7 @@ namespace Microsoft.PowerShell.Commands else { // Both -Property and -Value need to be specified if the user specifies the binary operation - if (_valueNotSpecified && (_binaryOperator != TokenKind.Ieq || !_forceBooleanEvaluation)) + if (_valueNotSpecified && ((_binaryOperator != TokenKind.Ieq && _binaryOperator != TokenKind.Not) || !_forceBooleanEvaluation)) { // The binary operation is specified explicitly by the user and the -Value parameter is // not specified @@ -1829,4 +1848,4 @@ namespace Microsoft.PowerShell.Commands #endregion Set-StrictMode #endregion Built-in cmdlets that are used by or require direct access to the engine. -} \ No newline at end of file +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 new file mode 100644 index 000000000..18e53c2e7 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 @@ -0,0 +1,128 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe "Where-Object" -Tags "CI" { + BeforeAll { + $Computers = @( + [PSCustomObject]@{ + ComputerName = "SPC-1234" + IPAddress = "192.168.0.1" + NumberOfCores = 1 + Drives = 'C','D' + }, + [PSCustomObject]@{ + ComputerName = "BGP-5678" + IPAddress = "" + NumberOfCores = 2 + Drives = 'C','D','E' + }, + [PSCustomObject]@{ + ComputerName = "MGC-9101" + NumberOfCores = 3 + Drives = 'C' + } + ) + } + + It "Where-Object -Not Prop" { + $Result = $Computers | Where-Object -Not 'IPAddress' + $Result.Count | Should -Be 2 + } + + It 'Where-Object -FilterScript {$true -ne $_.Prop}' { + $Result = $Computers | Where-Object -FilterScript {$true -ne $_.IPAddress} + $Result.Count | Should -Be 2 + } + + It "Where-Object Prop" { + $Result = $Computers | Where-Object 'IPAddress' + $Result.Count | Should -Be 1 + } + + It 'Where-Object -FilterScript {$true -eq $_.Prop}' { + $Result = $Computers | Where-Object -FilterScript {$true -eq $_.IPAddress} + $Result.Count | Should -Be 1 + } + + It 'Where-Object -FilterScript {$_.Prop -contains Value}' { + $Result = $Computers | Where-Object -FilterScript {$_.Drives -contains 'D'} + $Result.Count | Should -Be 2 + } + + It 'Where-Object Prop -contains Value' { + $Result = $Computers | Where-Object Drives -contains 'D' + $Result.Count | Should -Be 2 + } + + It 'Where-Object -FilterScript {$_.Prop -in $Array}' { + $Array = 'SPC-1234','BGP-5678' + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -in $Array} + $Result.Count | Should -Be 2 + } + + It 'Where-Object $Array -in Prop' { + $Array = 'SPC-1234','BGP-5678' + $Result = $Computers | Where-Object ComputerName -in $Array + $Result.Count | Should -Be 2 + } + + It 'Where-Object -FilterScript {$_.Prop -ge 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -ge 2} + $Result.Count | Should -Be 2 + } + + It 'Where-Object Prop -ge 2' { + $Result = $Computers | Where-Object NumberOfCores -ge 2 + $Result.Count | Should -Be 2 + } + + It 'Where-Object -FilterScript {$_.Prop -gt 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -gt 2} + $Result.Count | Should -Be 1 + } + + It 'Where-Object Prop -gt 2' { + $Result = $Computers | Where-Object NumberOfCores -gt 2 + $Result.Count | Should -Be 1 + } + + It 'Where-Object -FilterScript {$_.Prop -le 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -le 2} + $Result.Count | Should -Be 2 + } + + It 'Where-Object Prop -le 2' { + $Result = $Computers | Where-Object NumberOfCores -le 2 + $Result.Count | Should -Be 2 + } + + It 'Where-Object -FilterScript {$_.Prop -lt 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -lt 2} + $Result.Count | Should -Be 1 + } + + It 'Where-Object Prop -lt 2' { + $Result = $Computers | Where-Object NumberOfCores -lt 2 + $Result.Count | Should -Be 1 + } + + It 'Where-Object -FilterScript {$_.Prop -Like Value}' { + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -like 'MGC-9101'} + $Result.Count | Should -Be 1 + } + + It 'Where-Object Prop -like Value' { + $Result = $Computers | Where-Object ComputerName -like 'MGC-9101' + $Result.Count | Should -Be 1 + } + + It 'Where-Object -FilterScript {$_.Prop -Match Pattern}' { + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -match '^MGC.+'} + $Result.Count | Should -Be 1 + } + + It 'Where-Object Prop -like Value' { + $Result = $Computers | Where-Object ComputerName -match '^MGC.+' + $Result.Count | Should -Be 1 + } +}