diff --git a/src/Node/Statement/HaltCompilerStatement.php b/src/Node/Statement/HaltCompilerStatement.php index 009009fa..42013e71 100644 --- a/src/Node/Statement/HaltCompilerStatement.php +++ b/src/Node/Statement/HaltCompilerStatement.php @@ -20,17 +20,26 @@ class HaltCompilerStatement extends Expression { /** @var Token */ public $closeParen; - /** @var Token */ - public $semicolon; + /** @var Token (there is an implicit ')' before php close tags (`?>`)) */ + public $semicolonOrCloseTag; - /** @var Token */ + /** @var Token|null TokenKind::InlineHtml data unless there are no bytes (This is optional if there is nothing after the semicolon) */ public $data; const CHILD_NAMES = [ 'haltCompilerKeyword', 'openParen', 'closeParen', - 'semicolon', + 'semicolonOrCloseTag', 'data', ]; + + /** + * @return int + */ + public function getHaltCompilerOffset() { + // This accounts for the fact that PHP close tags may include a single newline, + // and that $this->data may be null. + return $this->semicolonOrCloseTag->getEndPosition(); + } } diff --git a/src/Parser.php b/src/Parser.php index cc3a4386..d618633b 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2858,15 +2858,27 @@ private function parseUnsetStatement($parentNode) { } private function parseHaltCompilerStatement($parentNode) { - $unsetStatement = new HaltCompilerStatement(); - $unsetStatement->parent = $parentNode; - - $unsetStatement->haltCompilerKeyword = $this->eat1(TokenKind::HaltCompilerKeyword); - $unsetStatement->openParen = $this->eat1(TokenKind::OpenParenToken); - $unsetStatement->closeParen = $this->eat1(TokenKind::CloseParenToken); - $unsetStatement->semicolon = $this->eatSemicolonOrAbortStatement(); - $unsetStatement->data = $this->eatOptional1(TokenKind::InlineHtml); - return $unsetStatement; + $haltCompilerStatement = new HaltCompilerStatement(); + $haltCompilerStatement->parent = $parentNode; + + $haltCompilerStatement->haltCompilerKeyword = $this->eat1(TokenKind::HaltCompilerKeyword); + $haltCompilerStatement->openParen = $this->eat1(TokenKind::OpenParenToken); + $haltCompilerStatement->closeParen = $this->eat1(TokenKind::CloseParenToken); + // There is an implicit ';' before the closing php tag. + $haltCompilerStatement->semicolonOrCloseTag = $this->eat(TokenKind::SemicolonToken, TokenKind::ScriptSectionEndTag); + // token_get_all() will return up to 3 tokens after __halt_compiler regardless of whether they're the right ones. + // For invalid php snippets, combine the remaining tokens into InlineHtml + $remainingTokens = []; + while ($this->token->kind !== TokenKind::EndOfFileToken) { + $remainingTokens[] = $this->token; + $this->advanceToken(); + } + if ($remainingTokens) { + $firstToken = $remainingTokens[0]; + $lastToken = end($remainingTokens); + $haltCompilerStatement->data = new Token(TokenKind::InlineHtml, $firstToken->fullStart, $firstToken->fullStart, $lastToken->fullStart + $lastToken->length - $firstToken->fullStart); + } + return $haltCompilerStatement; } private function parseArrayCreationExpression($parentNode) { diff --git a/src/Token.php b/src/Token.php index 6c9daf74..5fa1595d 100644 --- a/src/Token.php +++ b/src/Token.php @@ -18,7 +18,7 @@ class Token implements \JsonSerializable { public $fullStart; /** @var int */ public $start; - /** @var int */ + /** @var int the length is equal to $this->getEndPosition() - $this->fullStart. */ public $length; /** diff --git a/tests/cases/parser/haltCompiler1.php.tree b/tests/cases/parser/haltCompiler1.php.tree index 767be1b7..5603a69c 100644 --- a/tests/cases/parser/haltCompiler1.php.tree +++ b/tests/cases/parser/haltCompiler1.php.tree @@ -25,7 +25,7 @@ "kind": "CloseParenToken", "textLength": 1 }, - "semicolon": { + "semicolonOrCloseTag": { "kind": "SemicolonToken", "textLength": 1 }, diff --git a/tests/cases/parser/haltCompiler4.php.diag b/tests/cases/parser/haltCompiler4.php.diag index 35a59b76..a2a1cdee 100644 --- a/tests/cases/parser/haltCompiler4.php.diag +++ b/tests/cases/parser/haltCompiler4.php.diag @@ -16,29 +16,5 @@ "message": "';' expected.", "start": 32, "length": 0 - }, - { - "kind": 0, - "message": "Unexpected '::'", - "start": 32, - "length": 2 - }, - { - "kind": 0, - "message": "')' expected.", - "start": 38, - "length": 0 - }, - { - "kind": 0, - "message": "';' expected.", - "start": 38, - "length": 0 - }, - { - "kind": 0, - "message": "Unexpected 'InlineHtml'", - "start": 38, - "length": 3 } ] \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler4.php.tree b/tests/cases/parser/haltCompiler4.php.tree index fe930868..9ea4b6c7 100644 --- a/tests/cases/parser/haltCompiler4.php.tree +++ b/tests/cases/parser/haltCompiler4.php.tree @@ -27,58 +27,16 @@ "kind": "CloseParenToken", "textLength": 0 }, - "semicolon": { + "semicolonOrCloseTag": { "error": "MissingToken", "kind": "SemicolonToken", "textLength": 0 }, - "data": null - } - }, - { - "error": "SkippedToken", - "kind": "ColonColonToken", - "textLength": 2 - }, - { - "ExpressionStatement": { - "expression": { - "CallExpression": { - "callableExpression": { - "QualifiedName": { - "globalSpecifier": null, - "relativeSpecifier": null, - "nameParts": [ - { - "kind": "Name", - "textLength": 3 - } - ] - } - }, - "openParen": { - "kind": "OpenParenToken", - "textLength": 1 - }, - "argumentExpressionList": null, - "closeParen": { - "error": "MissingToken", - "kind": "CloseParenToken", - "textLength": 0 - } - } - }, - "semicolon": { - "error": "MissingToken", - "kind": "SemicolonToken", - "textLength": 0 + "data": { + "kind": "InlineHtml", + "textLength": 9 } } - }, - { - "error": "SkippedToken", - "kind": "InlineHtml", - "textLength": 3 } ], "endOfFileToken": { diff --git a/tests/cases/parser/haltCompiler5.php.diag b/tests/cases/parser/haltCompiler5.php.diag new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/cases/parser/haltCompiler5.php.diag @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler5.php.tree b/tests/cases/parser/haltCompiler5.php.tree new file mode 100644 index 00000000..b88050ec --- /dev/null +++ b/tests/cases/parser/haltCompiler5.php.tree @@ -0,0 +1,48 @@ +{ + "SourceFileNode": { + "statementList": [ + { + "InlineHtml": { + "scriptSectionEndTag": null, + "text": null, + "scriptSectionStartTag": { + "kind": "ScriptSectionStartTag", + "textLength": 6 + } + } + }, + { + "HaltCompilerStatement": { + "haltCompilerKeyword": { + "kind": "HaltCompilerKeyword", + "textLength": 15 + }, + "openParen": { + "kind": "OpenParenToken", + "textLength": 1 + }, + "closeParen": { + "kind": "CloseParenToken", + "textLength": 1 + }, + "semicolon": null, + "data": null + } + }, + { + "InlineHtml": { + "scriptSectionEndTag": { + "kind": "ScriptSectionEndTag", + "textLength": 3 + }, + "text": null, + "scriptSectionStartTag": null + } + } + ], + "endOfFileToken": { + "kind": "EndOfFileToken", + "textLength": 0 + } + } +} \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler6.php b/tests/cases/parser/haltCompiler6.php new file mode 100644 index 00000000..b54660b6 --- /dev/null +++ b/tests/cases/parser/haltCompiler6.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler6.php.diag b/tests/cases/parser/haltCompiler6.php.diag new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tests/cases/parser/haltCompiler6.php.diag @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler6.php.tree b/tests/cases/parser/haltCompiler6.php.tree new file mode 100644 index 00000000..50203000 --- /dev/null +++ b/tests/cases/parser/haltCompiler6.php.tree @@ -0,0 +1,41 @@ +{ + "SourceFileNode": { + "statementList": [ + { + "InlineHtml": { + "scriptSectionEndTag": null, + "text": null, + "scriptSectionStartTag": { + "kind": "ScriptSectionStartTag", + "textLength": 6 + } + } + }, + { + "HaltCompilerStatement": { + "haltCompilerKeyword": { + "kind": "HaltCompilerKeyword", + "textLength": 15 + }, + "openParen": { + "kind": "OpenParenToken", + "textLength": 1 + }, + "closeParen": { + "kind": "CloseParenToken", + "textLength": 1 + }, + "semicolonOrCloseTag": { + "kind": "ScriptSectionEndTag", + "textLength": 2 + }, + "data": null + } + } + ], + "endOfFileToken": { + "kind": "EndOfFileToken", + "textLength": 0 + } + } +} \ No newline at end of file diff --git a/tests/cases/parser/haltCompiler7.php b/tests/cases/parser/haltCompiler7.php new file mode 100644 index 00000000..35c0d324 --- /dev/null +++ b/tests/cases/parser/haltCompiler7.php @@ -0,0 +1,8 @@ +