From 2ea18ee6c99a9d9b7cb00988bd9e1e3e5a32fff7 Mon Sep 17 00:00:00 2001 From: Carl Morris Date: Tue, 9 Jun 2020 12:22:37 -0500 Subject: [PATCH] Flag `default` switch statement condition clause as keyword (#10487) --- .../engine/parser/Parser.cs | 52 +++--- .../engine/parser/token.cs | 9 +- .../engine/parser/tokenizer.cs | 4 +- .../Language/Parser/Parsing.Tests.ps1 | 162 ++++++++++++++++++ 4 files changed, 202 insertions(+), 25 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 6f13b76850..cb0ace0029 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -2746,28 +2746,42 @@ namespace System.Management.Automation.Language while (true) { - ExpressionAst clauseCondition = GetSingleCommandArgument(CommandArgumentContext.SwitchCondition); - if (clauseCondition == null) + Token token = PeekToken(); + bool isDefaultClause = token.Kind == TokenKind.Default; + ExpressionAst clauseCondition = null; + + if (isDefaultClause) { - // ErrorRecovery: if we don't have anything that looks like a condition, we won't - // find a body (because a body is just a script block, which works as a condition.) - // So don't look for a body, hope we find the '}' next. - - isError = true; - ReportIncompleteInput(After(endErrorStatement), - nameof(ParserStrings.MissingSwitchConditionExpression), - ParserStrings.MissingSwitchConditionExpression); - // Consume a closing curly, if there is one, to avoid an extra error - if (PeekToken().Kind == TokenKind.RCurly) + // Consume the 'default' token. + SkipToken(); + clauseCondition = new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord); + } + else + { + clauseCondition = GetSingleCommandArgument(CommandArgumentContext.SwitchCondition); + if (clauseCondition == null) { - SkipToken(); - } + // ErrorRecovery: if we don't have anything that looks like a condition, we won't + // find a body (because a body is just a script block, which works as a condition.) + // So don't look for a body, hope we find the '}' next. + isError = true; + ReportIncompleteInput(After(endErrorStatement), + nameof(ParserStrings.MissingSwitchConditionExpression), + ParserStrings.MissingSwitchConditionExpression); - break; + // Consume a closing curly, if there is one, to avoid an extra error + if (PeekToken().Kind == TokenKind.RCurly) + { + SkipToken(); + } + + break; + } } errorAsts.Add(clauseCondition); endErrorStatement = clauseCondition.Extent; + StatementBlockAst clauseBody = StatementBlockRule(); if (clauseBody == null) { @@ -2783,11 +2797,7 @@ namespace System.Management.Automation.Language errorAsts.Add(clauseBody); endErrorStatement = clauseBody.Extent; - var clauseConditionString = clauseCondition as StringConstantExpressionAst; - - if (clauseConditionString != null && - clauseConditionString.StringConstantType == StringConstantType.BareWord && - clauseConditionString.Value.Equals("default", StringComparison.OrdinalIgnoreCase)) + if (isDefaultClause) { if (@default != null) { @@ -2809,7 +2819,7 @@ namespace System.Management.Automation.Language SkipNewlinesAndSemicolons(); - Token token = PeekToken(); + token = PeekToken(); if (token.Kind == TokenKind.RCurly) { rCurly = token; diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index bfe7ddbd6a..3f78e299a8 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -585,6 +585,9 @@ namespace System.Management.Automation.Language /// The 'base' keyword Base = 168, + /// The 'default' keyword + Default = 169, + #endregion Keywords } @@ -944,6 +947,7 @@ namespace System.Management.Automation.Language /* Command */ TokenFlags.Keyword, /* Hidden */ TokenFlags.Keyword, /* Base */ TokenFlags.Keyword, + /* Default */ TokenFlags.Keyword, #endregion Flags for keywords }; @@ -1142,6 +1146,7 @@ namespace System.Management.Automation.Language /* Command */ "command", /* Hidden */ "hidden", /* Base */ "base", + /* Default */ "default", #endregion Text for keywords }; @@ -1149,9 +1154,9 @@ namespace System.Management.Automation.Language #if DEBUG static TokenTraits() { - Diagnostics.Assert(s_staticTokenFlags.Length == ((int)TokenKind.Base + 1), + Diagnostics.Assert(s_staticTokenFlags.Length == ((int)TokenKind.Default + 1), "Table size out of sync with enum - _staticTokenFlags"); - Diagnostics.Assert(s_tokenText.Length == ((int)TokenKind.Base + 1), + Diagnostics.Assert(s_tokenText.Length == ((int)TokenKind.Default + 1), "Table size out of sync with enum - _tokenText"); // Some random assertions to make sure the enum and the traits are in sync Diagnostics.Assert(GetTraits(TokenKind.Begin) == (TokenFlags.Keyword | TokenFlags.ScriptBlockBlockName), diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 3d15ebf4d8..57d438dcbf 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -629,7 +629,7 @@ namespace System.Management.Automation.Language /*A*/ "configuration", "public", "private", "static", /*A*/ /*B*/ "interface", "enum", "namespace", "module", /*B*/ /*C*/ "type", "assembly", "command", "hidden", /*C*/ - /*D*/ "base", /*D*/ + /*D*/ "base", "default", /*D*/ }; private static readonly TokenKind[] s_keywordTokenKind = new TokenKind[] { @@ -645,7 +645,7 @@ namespace System.Management.Automation.Language /*A*/ TokenKind.Configuration, TokenKind.Public, TokenKind.Private, TokenKind.Static, /*A*/ /*B*/ TokenKind.Interface, TokenKind.Enum, TokenKind.Namespace,TokenKind.Module, /*B*/ /*C*/ TokenKind.Type, TokenKind.Assembly, TokenKind.Command, TokenKind.Hidden, /*C*/ - /*D*/ TokenKind.Base, /*D*/ + /*D*/ TokenKind.Base, TokenKind.Default, /*D*/ }; internal static readonly string[] _operatorText = new string[] { diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 23a152cef1..9f213ecf14 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -479,3 +479,165 @@ Describe "ParserError type tests" -Tag CI { } } } + +Describe "Keywords 'default', 'hidden', 'in', 'static' Token parsing" -Tags CI { + BeforeAll { + $testCases_basic = @( + @{ + Script = 'switch (1) {default {0} 1 {1}}' + TokensToCheck = @{ + 5 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Default + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::Keyword + } + } + } + @{ + Script = 'switch (1) {"default" {0} 1 {1}}' + TokensToCheck = @{ + 5 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::StringExpandable + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + } + } + @{ + Script = 'switch (1) {adefault {0} 1 {1}}' + TokensToCheck = @{ + 5 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Identifier + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + } + } + @{ + Script = 'foreach ($i in 1..2) {$i}' + TokensToCheck = @{ + 3 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::In + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::Keyword + } + } + } + @{ + Script = 'class test {hidden $a; static aMethod () {return $this.a} }' + TokensToCheck = @{ + 3 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Hidden + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::Keyword + } + 6 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Static + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::Keyword + } + } + } + @{ + Script = 'echo default hidden in static' + TokensToCheck = @{ + 1 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Generic + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + 2 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Generic + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + 3 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Generic + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + 4 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Generic + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::None + } + } + } + @{ + Script = 'default' + TokensToCheck = @{ + 0 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Default + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword -bor [System.Management.Automation.Language.TokenFlags]::CommandName + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::CommandName + } + } + } + @{ + Script = 'hidden' + TokensToCheck = @{ + 0 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Hidden + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword -bor [System.Management.Automation.Language.TokenFlags]::CommandName + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::CommandName + } + } + } + @{ + Script = 'in' + TokensToCheck = @{ + 0 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::In + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword -bor [System.Management.Automation.Language.TokenFlags]::CommandName + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::CommandName + } + } + } + @{ + Script = 'static' + TokensToCheck = @{ + 0 = @{ + TokenKind = [System.Management.Automation.Language.TokenKind]::Static + TokenFlags_Mask = [System.Management.Automation.Language.TokenFlags]::Keyword -bor [System.Management.Automation.Language.TokenFlags]::CommandName + TokenFlags_Value = [System.Management.Automation.Language.TokenFlags]::CommandName + } + } + } + ) + } + + AfterAll { + } + + It "Keywords 'default', 'hidden', 'in', 'static' in {