Skip to content
28 changes: 24 additions & 4 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1620,15 +1620,37 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List<UsingStatementAst>
? lCurly.Extent
: (paramBlockAst != null) ? paramBlockAst.Extent : null;
IScriptExtent endExtent = null;
IScriptExtent extent;
IScriptExtent extent = null;
IScriptExtent scriptBlockExtent = null;

while (true)
{
Token blockNameToken = NextToken();
switch (blockNameToken.Kind)
{
default:
// Next token is unexpected.
// ErrorRecovery: if 'lCurly' is present, pretend we saw a closing curly; otherwise, eat the unexpected token.
if (lCurly != null)
{
UngetToken(blockNameToken);
scriptBlockExtent = ExtentOf(startExtent, endExtent);
}
else
{
// If "lCurly == null", then it's a ps1/psm1 file, and thus the extent is the whole file.
scriptBlockExtent = _tokenizer.GetScriptExtent();
}

// Report error about the unexpected token.
ReportError(blockNameToken.Extent, () => ParserStrings.MissingNamedBlocks, blockNameToken.Text);
goto return_script_block_ast;

case TokenKind.RCurly:
case TokenKind.EndOfInput:
// If the next token is RCurly or <eof>, handle it in 'CompleteScriptBlockBody'.
UngetToken(blockNameToken);
extent = ExtentOf(startExtent, endExtent);
goto finished_named_block_list;

case TokenKind.Dynamicparam:
Expand Down Expand Up @@ -1687,11 +1709,9 @@ private ScriptBlockAst NamedBlockListRule(Token lCurly, List<UsingStatementAst>
SkipNewlinesAndSemicolons();
}
finished_named_block_list:

IScriptExtent scriptBlockExtent;
extent = ExtentOf(startExtent, endExtent);
CompleteScriptBlockBody(lCurly, ref extent, out scriptBlockExtent);

return_script_block_ast:
return new ScriptBlockAst(scriptBlockExtent, usingStatements, paramBlockAst, beginBlock, processBlock, endBlock,
dynamicParamBlock);
}
Expand Down
3 changes: 3 additions & 0 deletions src/System.Management.Automation/resources/ParserStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,9 @@ The correct form is: foreach ($a in $b) {...}</value>
<data name="DuplicateScriptCommandClause" xml:space="preserve">
<value>Script command clause '{0}' has already been defined.</value>
</data>
<data name="MissingNamedBlocks" xml:space="preserve">
<value>unexpected token '{0}', expected 'begin', 'process', 'end', or 'dynamicparam'.</value>
</data>
<data name="MissingEndCurlyBrace" xml:space="preserve">
<value>Missing closing '}' in statement block or type definition.</value>
</data>
Expand Down
16 changes: 16 additions & 0 deletions test/powershell/Language/Parser/Parser.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -915,4 +915,20 @@ foo``u{2195}abc
# Issue #2780
{ ExecuteCommand "`$herestr=@`"`n'`"'`n`"@" } | Should Not Throw
}

It "Throw better error when statement should be put in named blocks - <name>" -TestCases @(
@{ script = "Function foo { [CmdletBinding()] param() DynamicParam {} Hi"; name = "function" }
@{ script = "{ begin {} Hi"; name = "script-block" }
@{ script = "begin {} Hi"; name = "script-file" }
) {
param($script)

$err = { ExecuteCommand $script } | Should -Throw -ErrorId "ParseException" -PassThru
$err.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "MissingNamedBlocks"
}

It "IncompleteParseException should be thrown when only ending curly is missing" {
$err = { ExecuteCommand "Function foo { [CmdletBinding()] param() DynamicParam {} " } | Should -Throw -ErrorId "IncompleteParseException" -PassThru
$err.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "MissingEndCurlyBrace"
}
}