Skip to content
15 changes: 14 additions & 1 deletion src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4514,7 +4514,7 @@ private MemberAst ClassMemberRule(string className, out List<Ast> astsOnError)
}
}

if (token.Kind == TokenKind.Identifier)
if (TryUseTokenAsSimpleName(token))
{
SkipToken();
var functionDefinition = MethodDeclarationRule(token, className, staticToken != null) as FunctionDefinitionAst;
Expand Down Expand Up @@ -4560,6 +4560,19 @@ private MemberAst ClassMemberRule(string className, out List<Ast> astsOnError)
return null;
}

private bool TryUseTokenAsSimpleName(Token token)
{
if (token.Kind == TokenKind.Identifier
|| token.Kind == TokenKind.DynamicKeyword
|| token.TokenFlags.HasFlag(TokenFlags.Keyword))
{
token.TokenFlags = TokenFlags.None;
return true;
}

return false;
}

private void RecordErrorAsts(Ast errAst, ref List<Ast> astsOnError)
{
if (errAst == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,119 @@ Describe 'Property Attributes Test' -Tags "CI" {
It "second value should be b" { $v.ValidValues[1] | Should -Be 'b' }
}

Describe 'Testing Method Names can be Keywords' -Tags "CI" {
BeforeAll {
[powershell] $script:PowerShell = [powershell]::Create()

function Invoke-PowerShell {
[CmdletBinding()]
param([string] $Script)

try {
$script:PowerShell.AddScript($Script).Invoke()
}
finally {
$script:PowerShell.Commands.Clear()
}
}

function Reset-PowerShell {
if ($script:PowerShell) {
$script:PowerShell.Dispose()
}

$script:PowerShell = [powershell]::Create()
}
}

AfterEach {
Reset-PowerShell
}

It 'Permits class methods to be named after keywords' {
$TestScript = @'
class TestMethodNames : IDisposable {
[string] Begin() { return "Begin" }

[string] Process() { return "Process" }

[string] End() { return "End" }

hidden $Data = "Secrets"

Dispose() {
$this.Data = [string]::Empty
}
}
$Object = [TestMethodNames]::new()
[PSCustomObject]@{
BeginTest = $Object.Begin()
ProcessTest = $Object.Process()
EndTest = $Object.End()
CheckData1 = $Object.Data
CheckData2 = $( $Object.Dispose(); $Object.Data )
}
'@
$Results = Invoke-PowerShell -Script $TestScript

$Results.BeginTest | Should -BeExactly 'Begin'
$Results.ProcessTest | Should -BeExactly 'Process'
$Results.EndTest | Should -BeExactly 'End'
$Results.CheckData1 | Should -BeExactly 'Secrets'
$Results.CheckData2 | Should -BeNullOrEmpty
}

It 'Permits class methods to be named after DynamicKeywords' {
$DefineKeyword = @'
function GetData {
param(
$KeywordData,
$Name,
$Value,
$SourceMetadata
)
end {
return $PSBoundParameters
}
}

$keyword = [System.Management.Automation.Language.DynamicKeyword]::new()
$keyword.NameMode = [System.Management.Automation.Language.DynamicKeywordNameMode]::SimpleNameRequired
$keyword.Keyword = 'GetData'
$keyword.BodyMode = [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable

$property = [System.Management.Automation.Language.DynamicKeywordProperty]::new()
$property.Name = 'Hey'
$property.TypeConstraint = 'int'
$keyword.Properties.Add('Hey', $property)

[System.Management.Automation.Language.DynamicKeyword]::AddKeyword($keyword)
'@
$DefineClass = @'
class Test {
hidden $Data = 'TOP SECRET'

[string] GetData() {
return $this.Data
}
}
'@
$TestScript = @'
$Object = [Test]::new()
$Object.GetData()
'@
Invoke-PowerShell -Script $DefineKeyword
Invoke-PowerShell -Script $DefineClass

Invoke-PowerShell -Script $TestScript | Should -BeExactly 'TOP SECRET'
}

AfterAll {
# Ensure we don't leave any dynamic keywords in the session.
[System.Management.Automation.Language.DynamicKeyword]::Reset()
}
}

Describe 'Method Attributes Test' -Tags "CI" {
class C { [Obsolete("aaa")][int]f() { return 1 } }

Expand Down