mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Move PSNullConditionalOperators feature out of experimental (#13529)
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -4243,7 +4243,7 @@ namespace System.Management.Automation.Language
|
||||
return NewToken(TokenKind.LBracket);
|
||||
}
|
||||
|
||||
if (ExperimentalFeature.IsEnabled("PSNullConditionalOperators") && c == '?')
|
||||
if (c == '?')
|
||||
{
|
||||
_tokenStart = _currentIndex;
|
||||
SkipChar();
|
||||
|
||||
@@ -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('
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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" ]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user