Skip to content

Commit

Permalink
Tokenizer/PHP: bugfix goto tokenization logic
Browse files Browse the repository at this point in the history
The logic could get confused when `goto` would be used in a mixed PHP/HTML file.

Includes adding a limited set of unit tests for the `T_GOTO_LABEL` tokenization.
  • Loading branch information
jrfnl committed Nov 28, 2020
1 parent cda358f commit 2351cc9
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="BackfillNumericSeparatorTest.php" role="test" />
<file baseinstalldir="" name="BitwiseOrTest.inc" role="test" />
<file baseinstalldir="" name="BitwiseOrTest.php" role="test" />
<file baseinstalldir="" name="GotoLabelTest.inc" role="test" />
<file baseinstalldir="" name="GotoLabelTest.php" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.inc" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.php" role="test" />
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.inc" role="test" />
Expand Down Expand Up @@ -2045,6 +2047,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
Expand Down Expand Up @@ -2117,6 +2121,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/BackfillNumericSeparatorTest.inc" name="tests/Core/Tokenizer/BackfillNumericSeparatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.php" name="tests/Core/Tokenizer/BitwiseOrTest.php" />
<install as="CodeSniffer/Core/Tokenizer/BitwiseOrTest.inc" name="tests/Core/Tokenizer/BitwiseOrTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.php" name="tests/Core/Tokenizer/GotoLabelTest.php" />
<install as="CodeSniffer/Core/Tokenizer/GotoLabelTest.inc" name="tests/Core/Tokenizer/GotoLabelTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
Expand Down
1 change: 1 addition & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,7 @@ function return types. We want to keep the parenthesis map clean,
$stopTokens = [
T_CASE => true,
T_SEMICOLON => true,
T_OPEN_TAG => true,
T_OPEN_CURLY_BRACKET => true,
T_INLINE_THEN => true,
];
Expand Down
53 changes: 53 additions & 0 deletions tests/Core/Tokenizer/GotoLabelTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/* testGotoStatement */
goto marker;
echo 'Foo';

/* testGotoDeclaration */
marker:
echo 'Bar';

/* testGotoStatementInLoop */
for($i=0,$j=50; $i<100; $i++) {
while($j--) {
if($j==17) GOTO end;
}
}
echo "i = $i";
?>
<div><?php $cond ? TEST_A : TEST_B ?></div>

<?php
/* testGotoDeclarationOutsideLoop */
end:
echo 'j hit 17';

switch ($x) {
/* testNotGotoDeclarationGlobalConstant */
case CONSTANT:
// Do something.
break;

/* testNotGotoDeclarationNamespacedConstant */
case MyNS\CONSTANT:
// Do something.
break;

/* testNotGotoDeclarationClassConstant */
case MyClass::CONSTANT:
// Do something.
break;

/* testNotGotoDeclarationClassProperty */
case $obj->property:
// Do something.
break;
}

switch (true) {
/* testNotGotoDeclarationGlobalConstantInTernary */
case $x === ($cond) ? CONST_A : CONST_B:
// Do something.
break;
}
171 changes: 171 additions & 0 deletions tests/Core/Tokenizer/GotoLabelTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php
/**
* Tests the tokenization of goto declarations and statements.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2020 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Tokenizer;

use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;

class GotoLabelTest extends AbstractMethodUnitTest
{


/**
* Verify that the label in a goto statement is tokenized as T_STRING.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to expect.
*
* @dataProvider dataGotoStatement
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testGotoStatement($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();

$label = $this->getTargetToken($testMarker, T_STRING);

$this->assertInternalType('int', $label);
$this->assertSame($testContent, $tokens[$label]['content']);

}//end testGotoStatement()


/**
* Data provider.
*
* @see testGotoStatement()
*
* @return array
*/
public function dataGotoStatement()
{
return [
[
'/* testGotoStatement */',
'marker',
],
[
'/* testGotoStatementInLoop */',
'end',
],
];

}//end dataGotoStatement()


/**
* Verify that the label in a goto declaration is tokenized as T_GOTO_LABEL.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to expect.
*
* @dataProvider dataGotoDeclaration
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testGotoDeclaration($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();

$label = $this->getTargetToken($testMarker, T_GOTO_LABEL);

$this->assertInternalType('int', $label);
$this->assertSame($testContent, $tokens[$label]['content']);

}//end testGotoDeclaration()


/**
* Data provider.
*
* @see testGotoDeclaration()
*
* @return array
*/
public function dataGotoDeclaration()
{
return [
[
'/* testGotoDeclaration */',
'marker:',
],
[
'/* testGotoDeclarationOutsideLoop */',
'end:',
],
];

}//end dataGotoDeclaration()


/**
* Verify that the constant used in a switch - case statement is not confused with a goto label.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to expect.
*
* @dataProvider dataNotAGotoDeclaration
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNotAGotoDeclaration($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_GOTO_LABEL, T_STRING], $testContent);

$this->assertSame(T_STRING, $tokens[$target]['code']);
$this->assertSame('T_STRING', $tokens[$target]['type']);

}//end testNotAGotoDeclaration()


/**
* Data provider.
*
* @see testNotAGotoDeclaration()
*
* @return array
*/
public function dataNotAGotoDeclaration()
{
return [
[
'/* testNotGotoDeclarationGlobalConstant */',
'CONSTANT',
],
[
'/* testNotGotoDeclarationNamespacedConstant */',
'CONSTANT',
],
[
'/* testNotGotoDeclarationClassConstant */',
'CONSTANT',
],
[
'/* testNotGotoDeclarationClassProperty */',
'property',
],
[
'/* testNotGotoDeclarationGlobalConstantInTernary */',
'CONST_A',
],
[
'/* testNotGotoDeclarationGlobalConstantInTernary */',
'CONST_B',
],
];

}//end dataNotAGotoDeclaration()


}//end class

0 comments on commit 2351cc9

Please sign in to comment.