diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 8f24333e27..ebb47e9be7 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -114,9 +114,6 @@ namespace System.Management.Automation name: "PSUnixFileStat", description: "Provide unix permission information for files and directories"), #endif - new ExperimentalFeature( - name: "PSNullConditionalOperators", - description: "Support the null conditional member access operators in PowerShell language"), new ExperimentalFeature( name: "PSCultureInvariantReplaceOperator", description: "Use culture invariant to-string convertor for lval in replace operator"), diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 5fdd4ac463..f896aeda37 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4243,7 +4243,7 @@ namespace System.Management.Automation.Language return NewToken(TokenKind.LBracket); } - if (ExperimentalFeature.IsEnabled("PSNullConditionalOperators") && c == '?') + if (c == '?') { _tokenStart = _currentIndex; SkipChar(); diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index b8b1cc607a..c42f30e510 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -3,7 +3,6 @@ Describe "TabCompletion" -Tags CI { BeforeAll { $separator = [System.IO.Path]::DirectorySeparatorChar - $nullConditionalFeatureDisabled = -not $EnabledExperimentalFeatures.Contains('PSNullConditionalOperators') } It 'Should complete Command' { @@ -34,12 +33,12 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly 'ToString(' } - It 'Should complete dotnet method with null conditional operator' -Skip:$nullConditionalFeatureDisabled { + It 'Should complete dotnet method with null conditional operator' { $res = TabExpansion2 -inputScript '(1)?.ToSt' -cursorColumn '(1)?.ToSt'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'ToString(' } - It 'Should complete dotnet method with null conditional operator without first letter' -Skip:$nullConditionalFeatureDisabled { + It 'Should complete dotnet method with null conditional operator without first letter' { $res = TabExpansion2 -inputScript '(1)?.' -cursorColumn '(1)?.'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'CompareTo(' } diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 5c2c65e4ad..237b4d8c98 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -260,31 +260,6 @@ Describe 'NullCoalesceOperations' -Tags 'CI' { Describe 'NullConditionalMemberAccess' -Tag 'CI' { - BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullConditionalOperators') - - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullConditionalOperators' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } - - function ExecuteTestIfFeatureIsEnabled([string] $TestContet) - { - if ($skipTest) { - Set-ItResult -Skipped -Because "PSNullConditionalOperators feature is disabled" - } else { - Invoke-Expression $testContent - } - } - } - - AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } - } - Context '?. operator tests' { BeforeAll { $psObj = [psobject]::new() @@ -302,8 +277,6 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { } It 'Can get member value of a non-null variable' { - - $testContent = @' ${psObj}?.name | Should -BeExactly 'value' ${array}?.length | Should -Be 3 ${hash}?.a | Should -Be 1 @@ -311,81 +284,50 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { (Get-Item $TestDrive)?.EnumerateFiles()?.Name | Should -BeExactly 'testfile.txt' [int32]::MaxValue?.ToString() | Should -BeExactly '2147483647' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Can get null when variable is null' { - $testContent = @' ${nonExistent}?.name | Should -BeNullOrEmpty ${nonExistent}?.MyMethod() | Should -BeNullOrEmpty (get-process -Name doesnotexist -ErrorAction SilentlyContinue)?.Id | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. operator multiple times in statement' { - $testContent = @' ${psObj}?.name?.nonExistent | Should -BeNullOrEmpty ${psObj}?.nonExistent?.nonExistent | Should -BeNullOrEmpty ${nonExistent}?.nonExistent?.nonExistent | Should -BeNullOrEmpty ${psObj}?.nested?.name | Should -BeExactly 'valuenested' ${psObj}?.nestedMethod?.GetHello() | Should -BeExactly 'hello' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name' { - $testContent = @' $methodName = 'ToLongDateString' (Get-Date '11/11/2019')?.$methodName() | Should -BeExactly 'Monday, November 11, 2019' ${doesNotExist}?.$methodName() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name that does not exist' { - $testContent = @' $methodName = 'DoesNotExist' { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name that does not exist' { - $testContent = @' $methodName = $null { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic property name' { - $testContent = @' $propName = 'SI' (Get-Process -Id $PID)?.$propName | Should -Be (Get-Process -id $PID).SessionId ${doesNotExist}?.$propName() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Should throw error when method does not exist' { - $testContent = @' { ${psObj}?.nestedMethod?.NonExistent() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } } @@ -401,46 +343,30 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { } It 'Can index can call properties' { - $testContent = @' ${array}?[0] | Should -Be 1 ${array}?[0,1] | Should -Be @(1,2) ${array}?[0..2] | Should -Be @(1,2,3) ${array}?[-2] | Should -Be 2 ${hash}?['a'] | Should -Be 1 -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Indexing in null items should be null' { - $testContent = @' ${doesnotExist}?[0] | Should -BeNullOrEmpty ${doesnotExist}?[0,1] | Should -BeNullOrEmpty ${doesnotExist}?[0..2] | Should -BeNullOrEmpty ${doesnotExist}?[-2] | Should -BeNullOrEmpty ${doesnotExist}?['a'] | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Can call methods on indexed items' { - $testContent = @' ${dateArray}?[0]?.ToLongDateString() | Should -BeExactly 'Friday, November 1, 2019' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Calling a method on nonexistent item give null' { - $testContent = @' ${dateArray}?[1234]?.ToLongDateString() | Should -BeNullOrEmpty ${doesNotExist}?[0]?.MyGetMethod() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } } } diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 9f213ecf14..c764830954 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -279,37 +279,18 @@ Describe 'null coalescing statement parsing' -Tag "CI" { } Describe 'null conditional member access statement parsing' -Tag 'CI' { - BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullConditionalOperators') + ShouldBeParseError '[datetime]?::now' ExpectedValueExpression, UnexpectedToken 11, 11 + ShouldBeParseError '$x ?.name' ExpectedValueExpression, UnexpectedToken 4, 4 + ShouldBeParseError 'Get-Date ?.ToString()' ExpectedExpression 20 + ShouldBeParseError '${x}?.' MissingPropertyName 6 + ShouldBeParseError '${x}?.name = "value"' InvalidLeftHandSide 0 - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullConditionalOperators' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } - } - - AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } - } - - # We need to add this check as parsing on script block is done before an `It` is called. - if (-not $skipTest) { - ShouldBeParseError '[datetime]?::now' ExpectedValueExpression, UnexpectedToken 11, 11 - ShouldBeParseError '$x ?.name' ExpectedValueExpression, UnexpectedToken 4, 4 - ShouldBeParseError 'Get-Date ?.ToString()' ExpectedExpression 20 - ShouldBeParseError '${x}?.' MissingPropertyName 6 - ShouldBeParseError '${x}?.name = "value"' InvalidLeftHandSide 0 - - ShouldBeParseError '[datetime]?[0]' MissingTypename, ExpectedValueExpression, UnexpectedToken 12, 11, 11 - ShouldBeParseError '${x} ?[1]' MissingTypename, ExpectedValueExpression, UnexpectedToken 7, 6, 6 - ShouldBeParseError '${x}?[]' MissingArrayIndexExpression 6 - ShouldBeParseError '${x}?[-]' MissingExpressionAfterOperator 7 - ShouldBeParseError '${x}?[ ]' MissingArrayIndexExpression 6 - ShouldBeParseError '${x}?[0] = 1' InvalidLeftHandSide 0 - } + ShouldBeParseError '[datetime]?[0]' MissingTypename, ExpectedValueExpression, UnexpectedToken 12, 11, 11 + ShouldBeParseError '${x} ?[1]' MissingTypename, ExpectedValueExpression, UnexpectedToken 7, 6, 6 + ShouldBeParseError '${x}?[]' MissingArrayIndexExpression 6 + ShouldBeParseError '${x}?[-]' MissingExpressionAfterOperator 7 + ShouldBeParseError '${x}?[ ]' MissingArrayIndexExpression 6 + ShouldBeParseError '${x}?[0] = 1' InvalidLeftHandSide 0 } Describe 'splatting parsing' -Tags "CI" { diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index db65e6ec21..66dc8572d3 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,10 +1,6 @@ { "ExperimentalFeatures": { "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ], - "PSNullConditionalOperators": [ - "test/powershell/Language/Operators/NullConditional.Tests.ps1", - "test/powershell/Language/Parser/Parsing.Tests.ps1", - "test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1" ], "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ], "Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1" ] }