Flag default switch statement condition clause as keyword (#10487)

This commit is contained in:
Carl Morris
2020-06-09 10:22:37 -07:00
committed by GitHub
parent 59dff00c0f
commit 2ea18ee6c9
4 changed files with 202 additions and 25 deletions
@@ -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;
@@ -585,6 +585,9 @@ namespace System.Management.Automation.Language
/// <summary>The 'base' keyword</summary>
Base = 168,
/// <summary>The 'default' keyword</summary>
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),
@@ -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[] {
@@ -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 {<Script>} correctly tokenized." -TestCases $testCases_basic {
param($Script, $TokensToCheck)
$tks = $null
$ers = $null
$result = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers)
foreach ($token in $TokensToCheck.Keys ) {
if ($TokensToCheck[$Token].ContainsKey('TokenKind')) {
$tks[$token].Kind | Should -Be $TokensToCheck[$token].TokenKind -Because 'because TokenKind must be as expected'
}
if ($TokensToCheck[$Token].ContainsKey('TokenFlags_Value')) {
$tks[$token].TokenFlags -band $TokensToCheck[$token].TokenFlags_Mask | Should -Be $TokensToCheck[$token].TokenFlags_Value -Because 'because TokenFlags must be as expected after masking'
}
}
}
$testKeywordsAsCmds = @(
@{ Keyword = 'default' }
@{ Keyword = 'hidden' }
@{ Keyword = 'in' } # Note: this overwrites Pester's `In` function.
@{ Keyword = 'static' }
)
It "<Keyword> can be used as command name" -TestCases $testKeywordsAsCmds {
param($Keyword)
Invoke-Expression "function $Keyword { '$Keyword' }"
. $Keyword | Should -BeExactly $Keyword
}
}