So you can see in the example above that the AST for a WhileStatement looks kind of like this (excluding that expression gibberish for clarity):
If you were to add curly braces around the call to buz.doSomething()and click "Go" again, you'd see that the AST would change a bit. It'd look like this:
WhileStatement
Expression
Statement
Block
BlockStatement
Statement
StatementExpression
Ah ha! We see that the curly braces add a couple more AST nodes - a Block and a BlockStatement. So all we have to do is write a rule to detect a WhileStatement that has a Statement that's not followed by a Block, and we've got a rule violation.
By the way, all this structural information - i.e., the fact that a Statement may be followed a Block - is concisely defined in the EBNF grammar. So, for example, the Statement definition looks like this:
void Statement() :
{}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD(2) LabeledStatement()
| Block()
| EmptyStatement()
| StatementExpression() ";"
| SwitchStatement()
| IfStatement()
| WhileStatement()
| DoStatement()
| ForStatement()
| BreakStatement()
| ContinueStatement()
| ReturnStatement()
| ThrowStatement()
| SynchronizedStatement()
| TryStatement()
}