app settings

This commit is contained in:
Thanakorn S
2026-04-27 10:50:31 +07:00
parent 76b9b2a2cb
commit d8c55d278a
3128 changed files with 630683 additions and 138 deletions
@@ -0,0 +1,64 @@
<?php
/**
* A test class for running all PHP_CodeSniffer unit tests.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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;
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === false) {
include_once 'Core/AllTests.php';
include_once 'Standards/AllSniffs.php';
} else {
include_once 'CodeSniffer/Core/AllTests.php';
include_once 'CodeSniffer/Standards/AllSniffs.php';
include_once 'FileList.php';
}
// PHPUnit 7 made the TestSuite run() method incompatible with
// older PHPUnit versions due to return type hints, so maintain
// two different suite objects.
$phpunit7 = false;
if (class_exists('\PHPUnit\Runner\Version') === true) {
$version = \PHPUnit\Runner\Version::id();
if ($version[0] === '7') {
$phpunit7 = true;
}
}
if ($phpunit7 === true) {
include_once 'TestSuite7.php';
} else {
include_once 'TestSuite.php';
}
class PHP_CodeSniffer_AllTests
{
/**
* Add all PHP_CodeSniffer test suites into a single test suite.
*
* @return \PHPUnit\Framework\TestSuite
*/
public static function suite()
{
$GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'] = [];
$GLOBALS['PHP_CODESNIFFER_TEST_DIRS'] = [];
// Use a special PHP_CodeSniffer test suite so that we can
// unset our autoload function after the run.
$suite = new TestSuite('PHP CodeSniffer');
$suite->addTest(Core\AllTests::suite());
$suite->addTest(Standards\AllSniffs::suite());
return $suite;
}//end suite()
}//end class
@@ -0,0 +1,140 @@
<?php
/**
* Base class to use when testing utility methods.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2018-2019 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Files\DummyFile;
use PHPUnit\Framework\TestCase;
abstract class AbstractMethodUnitTest extends TestCase
{
/**
* The file extension of the test case file (without leading dot).
*
* This allows child classes to overrule the default `inc` with, for instance,
* `js` or `css` when applicable.
*
* @var string
*/
protected static $fileExtension = 'inc';
/**
* The \PHP_CodeSniffer\Files\File object containing the parsed contents of the test case file.
*
* @var \PHP_CodeSniffer\Files\File
*/
protected static $phpcsFile;
/**
* Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file.
*
* The test case file for a unit test class has to be in the same directory
* directory and use the same file name as the test class, using the .inc extension.
*
* @return void
*/
public static function setUpBeforeClass()
{
$config = new Config();
$config->standards = ['PSR1'];
$ruleset = new Ruleset($config);
// Default to a file with the same name as the test class. Extension is property based.
$relativeCN = str_replace(__NAMESPACE__, '', get_called_class());
$relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativeCN);
$pathToTestFile = realpath(__DIR__).$relativePath.'.'.static::$fileExtension;
// Make sure the file gets parsed correctly based on the file type.
$contents = 'phpcs_input_file: '.$pathToTestFile.PHP_EOL;
$contents .= file_get_contents($pathToTestFile);
self::$phpcsFile = new DummyFile($contents, $ruleset, $config);
self::$phpcsFile->process();
}//end setUpBeforeClass()
/**
* Clean up after finished test.
*
* @return void
*/
public static function tearDownAfterClass()
{
self::$phpcsFile = null;
}//end tearDownAfterClass()
/**
* Get the token pointer for a target token based on a specific comment found on the line before.
*
* Note: the test delimiter comment MUST start with "/* test" to allow this function to
* distinguish between comments used *in* a test and test delimiters.
*
* @param string $commentString The delimiter comment to look for.
* @param int|string|array $tokenType The type of token(s) to look for.
* @param string $tokenContent Optional. The token content for the target token.
*
* @return int
*/
public function getTargetToken($commentString, $tokenType, $tokenContent=null)
{
$start = (self::$phpcsFile->numTokens - 1);
$comment = self::$phpcsFile->findPrevious(
T_COMMENT,
$start,
null,
false,
$commentString
);
$tokens = self::$phpcsFile->getTokens();
$end = ($start + 1);
// Limit the token finding to between this and the next delimiter comment.
for ($i = ($comment + 1); $i < $end; $i++) {
if ($tokens[$i]['code'] !== T_COMMENT) {
continue;
}
if (stripos($tokens[$i]['content'], '/* test') === 0) {
$end = $i;
break;
}
}
$target = self::$phpcsFile->findNext(
$tokenType,
($comment + 1),
$end,
false,
$tokenContent
);
if ($target === false) {
$msg = 'Failed to find test target token for comment string: '.$commentString;
if ($tokenContent !== null) {
$msg .= ' With token content: '.$tokenContent;
}
$this->assertFalse(true, $msg);
}
return $target;
}//end getTargetToken()
}//end class
@@ -0,0 +1,63 @@
<?php
/**
* A test class for testing the core.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2006-2019 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;
use PHP_CodeSniffer\Tests\FileList;
use PHPUnit\TextUI\TestRunner;
use PHPUnit\Framework\TestSuite;
class AllTests
{
/**
* Prepare the test runner.
*
* @return void
*/
public static function main()
{
TestRunner::run(self::suite());
}//end main()
/**
* Add all core unit tests into a test suite.
*
* @return \PHPUnit\Framework\TestSuite
*/
public static function suite()
{
$suite = new TestSuite('PHP CodeSniffer Core');
$testFileIterator = new FileList(__DIR__, '', '`Test\.php$`Di');
foreach ($testFileIterator->fileIterator as $file) {
if (strpos($file, 'AbstractMethodUnitTest.php') !== false) {
continue;
}
include_once $file;
$class = str_replace(__DIR__, '', $file);
$class = str_replace('.php', '', $class);
$class = str_replace('/', '\\', $class);
$class = 'PHP_CodeSniffer\Tests\Core'.$class;
$suite->addTestSuite($class);
}
return $suite;
}//end suite()
}//end class
@@ -0,0 +1,119 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Autoload::determineLoadedClass method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\Autoloader;
use PHP_CodeSniffer\Autoload;
use PHPUnit\Framework\TestCase;
class DetermineLoadedClassTest extends TestCase
{
/**
* Load the test files.
*
* @return void
*/
public static function setUpBeforeClass()
{
include __DIR__.'/TestFiles/Sub/C.inc';
}//end setUpBeforeClass()
/**
* Test for when class list is ordered.
*
* @return void
*/
public function testOrdered()
{
$classesBeforeLoad = [
'classes' => [],
'interfaces' => [],
'traits' => [],
];
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testOrdered()
/**
* Test for when class list is out of order.
*
* @return void
*/
public function testUnordered()
{
$classesBeforeLoad = [
'classes' => [],
'interfaces' => [],
'traits' => [],
];
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertEquals('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testUnordered()
}//end class
@@ -0,0 +1,3 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
class A {}
@@ -0,0 +1,4 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
require 'A.inc';
class B extends A {}
@@ -0,0 +1,4 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
require 'B.inc';
class C extends B {}
@@ -0,0 +1,5 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader\Sub;
require __DIR__.'/../C.inc';
use PHP_CodeSniffer\Tests\Core\Autoloader\C as ParentC;
class C extends ParentC {}
@@ -0,0 +1,105 @@
<?php
/* testSimpleAssignment */
$a = false;
/* testControlStructure */
while(true) {}
$a = 1;
/* testClosureAssignment */
$a = function($b=false;){};
/* testHeredocFunctionArg */
myFunction(<<<END
Foo
END
, 'bar');
/* testSwitch */
switch ($a) {
case 1: {break;}
default: {break;}
}
/* testStatementAsArrayValue */
$a = [new Datetime];
$a = array(new Datetime);
$a = new Datetime;
/* testUseGroup */
use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
$a = [
/* testArrowFunctionArrayValue */
'a' => fn() => return 1,
'b' => fn() => return 1,
];
/* testStaticArrowFunction */
static fn ($a) => $a;
return 0;
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
/* testArrowFunctionAsArgument */
$foo = foo(
fn() => bar()
);
/* testArrowFunctionWithArrayAsArgument */
$foo = foo(
fn() => [$row[0], $row[3]]
);
$match = match ($a) {
/* testMatchCase */
1 => 'foo',
/* testMatchDefault */
default => 'bar'
};
$match = match ($a) {
/* testMatchMultipleCase */
1, 2, => $a * $b,
/* testMatchDefaultComma */
default, => 'something'
};
match ($pressedKey) {
/* testMatchFunctionCall */
Key::RETURN_ => save($value, $user)
};
$result = match (true) {
/* testMatchFunctionCallArm */
str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
default => 'pl'
};
/* testMatchClosure */
$result = match ($key) {
1 => function($a, $b) {},
2 => function($b, $c) {},
};
/* testMatchArray */
$result = match ($key) {
1 => [1,2,3],
2 => [1 => one(), 2 => two()],
};
/* testNestedMatch */
$result = match ($key) {
1 => match ($key) {
1 => 'one',
2 => 'two',
},
2 => match ($key) {
1 => 'two',
2 => 'one',
},
};
@@ -0,0 +1,415 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:findEndOfStatement method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class FindEndOfStatementTest extends AbstractMethodUnitTest
{
/**
* Test a simple assignment.
*
* @return void
*/
public function testSimpleAssignment()
{
$start = $this->getTargetToken('/* testSimpleAssignment */', T_VARIABLE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testSimpleAssignment()
/**
* Test a direct call to a control structure.
*
* @return void
*/
public function testControlStructure()
{
$start = $this->getTargetToken('/* testControlStructure */', T_WHILE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 6), $found);
}//end testControlStructure()
/**
* Test the assignment of a closure.
*
* @return void
*/
public function testClosureAssignment()
{
$start = $this->getTargetToken('/* testClosureAssignment */', T_VARIABLE, '$a');
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 13), $found);
}//end testClosureAssignment()
/**
* Test using a heredoc in a function argument.
*
* @return void
*/
public function testHeredocFunctionArg()
{
// Find the end of the function.
$start = $this->getTargetToken('/* testHeredocFunctionArg */', T_STRING, 'myFunction');
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 10), $found);
// Find the end of the heredoc.
$start += 2;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 4), $found);
// Find the end of the last arg.
$start = ($found + 2);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame($start, $found);
}//end testHeredocFunctionArg()
/**
* Test parts of a switch statement.
*
* @return void
*/
public function testSwitch()
{
// Find the end of the switch.
$start = $this->getTargetToken('/* testSwitch */', T_SWITCH);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 28), $found);
// Find the end of the case.
$start += 9;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 8), $found);
// Find the end of default case.
$start += 11;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 6), $found);
}//end testSwitch()
/**
* Test statements that are array values.
*
* @return void
*/
public function testStatementAsArrayValue()
{
// Test short array syntax.
$start = $this->getTargetToken('/* testStatementAsArrayValue */', T_NEW);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 2), $found);
// Test long array syntax.
$start += 12;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 2), $found);
// Test same statement outside of array.
$start += 10;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 3), $found);
}//end testStatementAsArrayValue()
/**
* Test a use group.
*
* @return void
*/
public function testUseGroup()
{
$start = $this->getTargetToken('/* testUseGroup */', T_USE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 23), $found);
}//end testUseGroup()
/**
* Test arrow function as array value.
*
* @return void
*/
public function testArrowFunctionArrayValue()
{
$start = $this->getTargetToken('/* testArrowFunctionArrayValue */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 9), $found);
}//end testArrowFunctionArrayValue()
/**
* Test static arrow function.
*
* @return void
*/
public function testStaticArrowFunction()
{
$static = $this->getTargetToken('/* testStaticArrowFunction */', T_STATIC);
$fn = $this->getTargetToken('/* testStaticArrowFunction */', T_FN);
$endOfStatementStatic = self::$phpcsFile->findEndOfStatement($static);
$endOfStatementFn = self::$phpcsFile->findEndOfStatement($fn);
$this->assertSame($endOfStatementFn, $endOfStatementStatic);
}//end testStaticArrowFunction()
/**
* Test arrow function with return value.
*
* @return void
*/
public function testArrowFunctionReturnValue()
{
$start = $this->getTargetToken('/* testArrowFunctionReturnValue */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 18), $found);
}//end testArrowFunctionReturnValue()
/**
* Test arrow function used as a function argument.
*
* @return void
*/
public function testArrowFunctionAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionAsArgument */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 8), $found);
}//end testArrowFunctionAsArgument()
/**
* Test arrow function with arrays used as a function argument.
*
* @return void
*/
public function testArrowFunctionWithArrayAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionWithArrayAsArgument */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 17), $found);
}//end testArrowFunctionWithArrayAsArgument()
/**
* Test simple match expression case.
*
* @return void
*/
public function testMatchCase()
{
$start = $this->getTargetToken('/* testMatchCase */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
$start = $this->getTargetToken('/* testMatchCase */', T_CONSTANT_ENCAPSED_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 1), $found);
}//end testMatchCase()
/**
* Test simple match expression default case.
*
* @return void
*/
public function testMatchDefault()
{
$start = $this->getTargetToken('/* testMatchDefault */', T_MATCH_DEFAULT);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 4), $found);
$start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefault()
/**
* Test multiple comma-separated match expression case values.
*
* @return void
*/
public function testMatchMultipleCase()
{
$start = $this->getTargetToken('/* testMatchMultipleCase */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 13), $found);
$start += 6;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 7), $found);
}//end testMatchMultipleCase()
/**
* Test match expression default case with trailing comma.
*
* @return void
*/
public function testMatchDefaultComma()
{
$start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_DEFAULT);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testMatchDefaultComma()
/**
* Test match expression with function call.
*
* @return void
*/
public function testMatchFunctionCall()
{
$start = $this->getTargetToken('/* testMatchFunctionCall */', T_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 12), $found);
$start += 8;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 1), $found);
}//end testMatchFunctionCall()
/**
* Test match expression with function call in the arm.
*
* @return void
*/
public function testMatchFunctionCallArm()
{
// Check the first case.
$start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 21), $found);
// Check the second case.
$start += 24;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 21), $found);
}//end testMatchFunctionCallArm()
/**
* Test match expression with closure.
*
* @return void
*/
public function testMatchClosure()
{
$start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 14), $found);
$start += 17;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 14), $found);
}//end testMatchClosure()
/**
* Test match expression with array declaration.
*
* @return void
*/
public function testMatchArray()
{
$start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 11), $found);
$start += 14;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 22), $found);
}//end testMatchArray()
/**
* Test nested match expressions.
*
* @return void
*/
public function testNestedMatch()
{
$start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 30), $found);
$start += 21;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testNestedMatch()
}//end class
@@ -0,0 +1,37 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\File;
class testFECNClass {}
/* testExtendedClass */
class testFECNExtendedClass extends testFECNClass {}
/* testNamespacedClass */
class testFECNNamespacedClass extends \PHP_CodeSniffer\Tests\Core\File\testFECNClass {}
/* testNonExtendedClass */
class testFECNNonExtendedClass {}
/* testInterface */
interface testFECNInterface {}
/* testInterfaceThatExtendsInterface */
interface testInterfaceThatExtendsInterface extends testFECNInterface{}
/* testInterfaceThatExtendsFQCNInterface */
interface testInterfaceThatExtendsFQCNInterface extends \PHP_CodeSniffer\Tests\Core\File\testFECNInterface{}
/* testNestedExtendedClass */
class testFECNNestedExtendedClass {
public function someMethod() {
/* testNestedExtendedAnonClass */
$anon = new class extends testFECNAnonClass {};
}
}
/* testClassThatExtendsAndImplements */
class testFECNClassThatExtendsAndImplements extends testFECNClass implements InterfaceA, InterfaceB {}
/* testClassThatImplementsAndExtends */
class testFECNClassThatImplementsAndExtends implements InterfaceA, InterfaceB extends testFECNClass {}
@@ -0,0 +1,93 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:findExtendedClassName method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class FindExtendedClassNameTest extends AbstractMethodUnitTest
{
/**
* Test retrieving the name of the class being extended by another class
* (or interface).
*
* @param string $identifier Comment which precedes the test case.
* @param bool $expected Expected function output.
*
* @dataProvider dataExtendedClass
*
* @return void
*/
public function testFindExtendedClassName($identifier, $expected)
{
$OOToken = $this->getTargetToken($identifier, [T_CLASS, T_ANON_CLASS, T_INTERFACE]);
$result = self::$phpcsFile->findExtendedClassName($OOToken);
$this->assertSame($expected, $result);
}//end testFindExtendedClassName()
/**
* Data provider for the FindExtendedClassName test.
*
* @see testFindExtendedClassName()
*
* @return array
*/
public function dataExtendedClass()
{
return [
[
'/* testExtendedClass */',
'testFECNClass',
],
[
'/* testNamespacedClass */',
'\PHP_CodeSniffer\Tests\Core\File\testFECNClass',
],
[
'/* testNonExtendedClass */',
false,
],
[
'/* testInterface */',
false,
],
[
'/* testInterfaceThatExtendsInterface */',
'testFECNInterface',
],
[
'/* testInterfaceThatExtendsFQCNInterface */',
'\PHP_CodeSniffer\Tests\Core\File\testFECNInterface',
],
[
'/* testNestedExtendedClass */',
false,
],
[
'/* testNestedExtendedAnonClass */',
'testFECNAnonClass',
],
[
'/* testClassThatExtendsAndImplements */',
'testFECNClass',
],
[
'/* testClassThatImplementsAndExtends */',
'testFECNClass',
],
];
}//end dataExtendedClass()
}//end class
@@ -0,0 +1,35 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\File;
interface testFIINInterface2 {}
/* testInterface */
interface testFIINInterface {}
/* testImplementedClass */
class testFIINImplementedClass implements testFIINInterface {}
/* testMultiImplementedClass */
class testFIINMultiImplementedClass implements testFIINInterface, testFIINInterface2 {}
/* testNamespacedClass */
class testFIINNamespacedClass implements \PHP_CodeSniffer\Tests\Core\File\testFIINInterface {}
/* testNonImplementedClass */
class testFIINNonImplementedClass {}
/* testClassThatExtendsAndImplements */
class testFECNClassThatExtendsAndImplements extends testFECNClass implements InterfaceA, \NameSpaced\Cat\InterfaceB {}
/* testClassThatImplementsAndExtends */
class testFECNClassThatImplementsAndExtends implements \InterfaceA, InterfaceB extends testFECNClass {}
/* testBackedEnumWithoutImplements */
enum Suit:string {}
/* testEnumImplements */
enum Suit implements Colorful {}
/* testBackedEnumImplements */
enum Suit: string implements Colorful, \Deck {}
@@ -0,0 +1,104 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:findImplementedInterfaceNames method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class FindImplementedInterfaceNamesTest extends AbstractMethodUnitTest
{
/**
* Test retrieving the name(s) of the interfaces being implemented by a class.
*
* @param string $identifier Comment which precedes the test case.
* @param bool $expected Expected function output.
*
* @dataProvider dataImplementedInterface
*
* @return void
*/
public function testFindImplementedInterfaceNames($identifier, $expected)
{
$OOToken = $this->getTargetToken($identifier, [T_CLASS, T_ANON_CLASS, T_INTERFACE, T_ENUM]);
$result = self::$phpcsFile->findImplementedInterfaceNames($OOToken);
$this->assertSame($expected, $result);
}//end testFindImplementedInterfaceNames()
/**
* Data provider for the FindImplementedInterfaceNames test.
*
* @see testFindImplementedInterfaceNames()
*
* @return array
*/
public function dataImplementedInterface()
{
return [
[
'/* testImplementedClass */',
['testFIINInterface'],
],
[
'/* testMultiImplementedClass */',
[
'testFIINInterface',
'testFIINInterface2',
],
],
[
'/* testNamespacedClass */',
['\PHP_CodeSniffer\Tests\Core\File\testFIINInterface'],
],
[
'/* testNonImplementedClass */',
false,
],
[
'/* testInterface */',
false,
],
[
'/* testClassThatExtendsAndImplements */',
[
'InterfaceA',
'\NameSpaced\Cat\InterfaceB',
],
],
[
'/* testClassThatImplementsAndExtends */',
[
'\InterfaceA',
'InterfaceB',
],
],
[
'/* testBackedEnumWithoutImplements */',
false,
],
[
'/* testEnumImplements */',
['Colorful'],
],
[
'/* testBackedEnumImplements */',
[
'Colorful',
'\Deck',
],
],
];
}//end dataImplementedInterface()
}//end class
@@ -0,0 +1,123 @@
<?php
/* testSimpleAssignment */
$a = false;
/* testFunctionCall */
$a = doSomething();
/* testFunctionCallArgument */
$a = doSomething($a, $b);
/* testControlStructure */
while(true) {}
$a = 1;
/* testClosureAssignment */
$a = function($b=false;){};
/* testHeredocFunctionArg */
myFunction(<<<END
Foo
END
, 'bar');
switch ($a) {
case 1: {break;}
case 2: $foo = true; break;
default: {break;}
/* testSwitch */
}
/* testStatementAsArrayValue */
$a = [new Datetime];
$a = array(new Datetime);
$a = ['a' => $foo + $bar, 'b' => true];
/* testUseGroup */
use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
$a = [
/* testArrowFunctionArrayValue */
'a' => fn() => return 1,
'b' => fn() => return 1,
];
/* testStaticArrowFunction */
static fn ($a) => $a;
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
/* testArrowFunctionAsArgument */
$foo = foo(
fn() => bar()
);
/* testArrowFunctionWithArrayAsArgument */
$foo = foo(
fn() => [$row[0], $row[3]]
);
$match = match ($a) {
/* testMatchCase */
1 => 'foo',
/* testMatchDefault */
default => 'bar'
};
$match = match ($a) {
/* testMatchMultipleCase */
1, 2, => $a * $b,
/* testMatchDefaultComma */
default, => 'something'
};
match ($pressedKey) {
/* testMatchFunctionCall */
Key::RETURN_ => save($value, $user)
};
$result = match (true) {
/* testMatchFunctionCallArm */
str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
default => 'pl'
};
/* testMatchClosure */
$result = match ($key) {
1 => function($a, $b) {},
2 => function($b, $c) {},
};
/* testMatchArray */
$result = match ($key) {
1 => [1,2,3],
2 => [1 => one($a, $b), 2 => two($b, $c)],
3 => [],
};
/* testNestedMatch */
$result = match ($key) {
1 => match ($key) {
1 => 'one',
2 => 'two',
},
2 => match ($key) {
1 => 'two',
2 => 'one',
},
};
return 0;
/* testOpenTag */
?>
<h1>Test</h1>
<?php echo '<h2>', foo(), '</h2>';
/* testOpenTagWithEcho */
?>
<h1>Test</h1>
<?= '<h2>', foo(), '</h2>';
@@ -0,0 +1,503 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:findStartOfStatement method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class FindStartOfStatementTest extends AbstractMethodUnitTest
{
/**
* Test a simple assignment.
*
* @return void
*/
public function testSimpleAssignment()
{
$start = $this->getTargetToken('/* testSimpleAssignment */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
}//end testSimpleAssignment()
/**
* Test a function call.
*
* @return void
*/
public function testFunctionCall()
{
$start = $this->getTargetToken('/* testFunctionCall */', T_CLOSE_PARENTHESIS);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testFunctionCall()
/**
* Test a function call.
*
* @return void
*/
public function testFunctionCallArgument()
{
$start = $this->getTargetToken('/* testFunctionCallArgument */', T_VARIABLE, '$b');
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testFunctionCallArgument()
/**
* Test a direct call to a control structure.
*
* @return void
*/
public function testControlStructure()
{
$start = $this->getTargetToken('/* testControlStructure */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testControlStructure()
/**
* Test the assignment of a closure.
*
* @return void
*/
public function testClosureAssignment()
{
$start = $this->getTargetToken('/* testClosureAssignment */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 12), $found);
}//end testClosureAssignment()
/**
* Test using a heredoc in a function argument.
*
* @return void
*/
public function testHeredocFunctionArg()
{
// Find the start of the function.
$start = $this->getTargetToken('/* testHeredocFunctionArg */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
// Find the start of the heredoc.
$start -= 4;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 4), $found);
// Find the start of the last arg.
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testHeredocFunctionArg()
/**
* Test parts of a switch statement.
*
* @return void
*/
public function testSwitch()
{
// Find the start of the switch.
$start = $this->getTargetToken('/* testSwitch */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 47), $found);
// Find the start of default case.
$start -= 5;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
// Find the start of the second case.
$start -= 12;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
// Find the start of the first case.
$start -= 13;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 8), $found);
// Test inside the first case.
$start--;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testSwitch()
/**
* Test statements that are array values.
*
* @return void
*/
public function testStatementAsArrayValue()
{
// Test short array syntax.
$start = $this->getTargetToken('/* testStatementAsArrayValue */', T_STRING, 'Datetime');
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
// Test long array syntax.
$start += 12;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
// Test same statement outside of array.
$start++;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 9), $found);
// Test with an array index.
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
}//end testStatementAsArrayValue()
/**
* Test a use group.
*
* @return void
*/
public function testUseGroup()
{
$start = $this->getTargetToken('/* testUseGroup */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 23), $found);
}//end testUseGroup()
/**
* Test arrow function as array value.
*
* @return void
*/
public function testArrowFunctionArrayValue()
{
$start = $this->getTargetToken('/* testArrowFunctionArrayValue */', T_COMMA);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 9), $found);
}//end testArrowFunctionArrayValue()
/**
* Test static arrow function.
*
* @return void
*/
public function testStaticArrowFunction()
{
$start = $this->getTargetToken('/* testStaticArrowFunction */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 11), $found);
}//end testStaticArrowFunction()
/**
* Test arrow function with return value.
*
* @return void
*/
public function testArrowFunctionReturnValue()
{
$start = $this->getTargetToken('/* testArrowFunctionReturnValue */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
}//end testArrowFunctionReturnValue()
/**
* Test arrow function used as a function argument.
*
* @return void
*/
public function testArrowFunctionAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionAsArgument */', T_FN);
$start += 8;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 8), $found);
}//end testArrowFunctionAsArgument()
/**
* Test arrow function with arrays used as a function argument.
*
* @return void
*/
public function testArrowFunctionWithArrayAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionWithArrayAsArgument */', T_FN);
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 17), $found);
}//end testArrowFunctionWithArrayAsArgument()
/**
* Test simple match expression case.
*
* @return void
*/
public function testMatchCase()
{
$start = $this->getTargetToken('/* testMatchCase */', T_COMMA);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testMatchCase()
/**
* Test simple match expression default case.
*
* @return void
*/
public function testMatchDefault()
{
$start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING, "'bar'");
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefault()
/**
* Test multiple comma-separated match expression case values.
*
* @return void
*/
public function testMatchMultipleCase()
{
$start = $this->getTargetToken('/* testMatchMultipleCase */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
$start += 6;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 4), $found);
}//end testMatchMultipleCase()
/**
* Test match expression default case with trailing comma.
*
* @return void
*/
public function testMatchDefaultComma()
{
$start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 3), $found);
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefaultComma()
/**
* Test match expression with function call.
*
* @return void
*/
public function testMatchFunctionCall()
{
$start = $this->getTargetToken('/* testMatchFunctionCall */', T_CLOSE_PARENTHESIS);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testMatchFunctionCall()
/**
* Test match expression with function call in the arm.
*
* @return void
*/
public function testMatchFunctionCallArm()
{
// Check the first case.
$start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
// Check the second case.
$start += 24;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
}//end testMatchFunctionCallArm()
/**
* Test match expression with closure.
*
* @return void
*/
public function testMatchClosure()
{
$start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER);
$start += 14;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
}//end testMatchClosure()
/**
* Test match expression with array declaration.
*
* @return void
*/
public function testMatchArray()
{
// Start of first case statement.
$start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
// Comma after first statement.
$start += 11;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 7), $found);
// Start of second case statement.
$start += 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
// Comma after first statement.
$start += 30;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 26), $found);
}//end testMatchArray()
/**
* Test nested match expressions.
*
* @return void
*/
public function testNestedMatch()
{
$start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER);
$start += 30;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 26), $found);
$start -= 4;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
$start -= 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
}//end testNestedMatch()
/**
* Test PHP open tag.
*
* @return void
*/
public function testOpenTag()
{
$start = $this->getTargetToken('/* testOpenTag */', T_OPEN_TAG);
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testOpenTag()
/**
* Test PHP short open echo tag.
*
* @return void
*/
public function testOpenTagWithEcho()
{
$start = $this->getTargetToken('/* testOpenTagWithEcho */', T_OPEN_TAG_WITH_ECHO);
$start += 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testOpenTagWithEcho()
}//end class
@@ -0,0 +1,304 @@
<?php
class TestMemberProperties
{
/* testVar */
var $varA = true;
/* testVarType */
var ?int $varA = true;
/* testPublic */
public $varB = true;
/* testPublicType */
public string $varB = true;
/* testProtected */
protected $varC = true;
/* testProtectedType */
protected bool $varC = true;
/* testPrivate */
private $varD = true;
/* testPrivateType */
private array $varD = true;
/* testStatic */
static $varE = true;
/* testStaticType */
static ?string $varE = true;
/* testStaticVar */
static var $varF = true;
/* testVarStatic */
var static $varG = true;
/* testPublicStatic */
public static $varH = true;
/* testProtectedStatic */
static protected $varI = true;
/* testPrivateStatic */
private static $varJ = true;
/* testNoPrefix */
$varK = true;
/* testPublicStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
public static $varH = true;
/* testProtectedStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
static protected $varI = true;
/* testPrivateStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
private static $varJ = true;
public float
/* testGroupType 1 */
$x,
/* testGroupType 2 */
$y;
public static ?string
/* testGroupNullableType 1 */
$x = null,
/* testGroupNullableType 2 */
$y = null;
protected static
/* testGroupProtectedStatic 1 */
$varL,
/* testGroupProtectedStatic 2 */
$varM,
/* testGroupProtectedStatic 3 */
$varN;
private
/* testGroupPrivate 1 */
$varO = true,
/* testGroupPrivate 2 */
$varP = array( 'a' => 'a', 'b' => 'b' ),
/* testGroupPrivate 3 */
$varQ = 'string',
/* testGroupPrivate 4 */
$varR = 123,
/* testGroupPrivate 5 */
$varS = ONE / self::THREE,
/* testGroupPrivate 6 */
$varT = [
'a' => 'a',
'b' => 'b'
],
/* testGroupPrivate 7 */
$varU = __DIR__ . "/base";
/* testMethodParam */
public function methodName($param) {
/* testImportedGlobal */
global $importedGlobal = true;
/* testLocalVariable */
$localVariable = true;
}
/* testPropertyAfterMethod */
private static $varV = true;
/* testMessyNullableType */
public /* comment
*/ ? //comment
array $foo = [];
/* testNamespaceType */
public \MyNamespace\MyClass $foo;
/* testNullableNamespaceType 1 */
private ?ClassName $nullableClassType;
/* testNullableNamespaceType 2 */
protected ?Folder\ClassName $nullableClassType2;
/* testMultilineNamespaceType */
public \MyNamespace /** comment *\/ comment */
\MyClass /* comment */
\Foo $foo;
}
interface Base
{
/* testInterfaceProperty */
protected $anonymous;
}
/* testGlobalVariable */
$globalVariable = true;
/* testNotAVariable */
return;
$a = ( $foo == $bar ? new stdClass() :
new class() {
/* testNestedProperty 1 */
public $var = true;
/* testNestedMethodParam 1 */
public function something($var = false) {}
}
);
function_call( 'param', new class {
/* testNestedProperty 2 */
public $year = 2017;
/* testNestedMethodParam 2 */
public function __construct( $open, $post_id ) {}
}, 10, 2 );
class PHP8Mixed {
/* testPHP8MixedTypeHint */
public static miXed $mixed;
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
private ?mixed $nullableMixed;
}
class NSOperatorInType {
/* testNamespaceOperatorTypeHint */
public ?namespace\Name $prop;
}
$anon = class() {
/* testPHP8UnionTypesSimple */
public int|float $unionTypeSimple;
/* testPHP8UnionTypesTwoClasses */
private MyClassA|\Package\MyClassB $unionTypesTwoClasses;
/* testPHP8UnionTypesAllBaseTypes */
protected array|bool|int|float|NULL|object|string $unionTypesAllBaseTypes;
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
var false|mixed|self|parent|iterable|Resource $unionTypesAllPseudoTypes;
/* testPHP8UnionTypesIllegalTypes */
// Intentional fatal error - types which are not allowed for properties, but that's not the concern of the method.
public callable|static|void $unionTypesIllegalTypes;
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
public ?int|float $unionTypesNullable;
/* testPHP8PseudoTypeNull */
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
public null $pseudoTypeNull;
/* testPHP8PseudoTypeFalse */
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
public false $pseudoTypeFalse;
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
public bool|FALSE $pseudoTypeFalseAndBool;
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
public object|ClassName $objectAndClass;
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
public iterable|array|Traversable $pseudoTypeIterableAndArray;
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
public int |string| /*comment*/ INT $duplicateTypeInUnion;
/* testPHP81Readonly */
public readonly int $readonly;
/* testPHP81ReadonlyWithNullableType */
public readonly ?array $array;
/* testPHP81ReadonlyWithUnionType */
public readonly string|int $readonlyWithUnionType;
/* testPHP81ReadonlyWithUnionTypeWithNull */
protected ReadOnly string|null $readonlyWithUnionTypeWithNull;
/* testPHP81OnlyReadonlyWithUnionType */
readonly string|int $onlyReadonly;
};
$anon = class {
/* testPHP8PropertySingleAttribute */
#[PropertyWithAttribute]
public string $foo;
/* testPHP8PropertyMultipleAttributes */
#[PropertyWithAttribute(foo: 'bar'), MyAttribute]
protected ?int|float $bar;
/* testPHP8PropertyMultilineAttribute */
#[
PropertyWithAttribute(/* comment */ 'baz')
]
private mixed $baz;
};
enum Suit
{
/* testEnumProperty */
protected $anonymous;
}
enum Direction implements ArrayAccess
{
case Up;
case Down;
/* testEnumMethodParamNotProperty */
public function offsetGet($val) { ... }
}
$anon = class() {
/* testPHP81IntersectionTypes */
public Foo&Bar $intersectionType;
/* testPHP81MoreIntersectionTypes */
public Foo&Bar&Baz $moreIntersectionTypes;
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method.
public int&string $illegalIntersectionType;
/* testPHP81NulltableIntersectionType */
// Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method.
public ?Foo&Bar $nullableIntersectionType;
};
@@ -0,0 +1,864 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getMemberProperties method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class GetMemberPropertiesTest extends AbstractMethodUnitTest
{
/**
* Test the getMemberProperties() method.
*
* @param string $identifier Comment which precedes the test case.
* @param bool $expected Expected function output.
*
* @dataProvider dataGetMemberProperties
*
* @return void
*/
public function testGetMemberProperties($identifier, $expected)
{
$variable = $this->getTargetToken($identifier, T_VARIABLE);
$result = self::$phpcsFile->getMemberProperties($variable);
$this->assertArraySubset($expected, $result, true);
}//end testGetMemberProperties()
/**
* Data provider for the GetMemberProperties test.
*
* @see testGetMemberProperties()
*
* @return array
*/
public function dataGetMemberProperties()
{
return [
[
'/* testVar */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testVarType */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'is_readonly' => false,
'type' => '?int',
'nullable_type' => true,
],
],
[
'/* testPublic */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'string',
'nullable_type' => false,
],
],
[
'/* testProtected */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedType */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'bool',
'nullable_type' => false,
],
],
[
'/* testPrivate */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateType */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'array',
'nullable_type' => false,
],
],
[
'/* testStatic */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testStaticType */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testStaticVar */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testVarStatic */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicStatic */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedStatic */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateStatic */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testNoPrefix */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicStaticWithDocblock */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedStaticWithDocblock */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateStaticWithDocblock */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupType 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'float',
'nullable_type' => false,
],
],
[
'/* testGroupType 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'float',
'nullable_type' => false,
],
],
[
'/* testGroupNullableType 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testGroupNullableType 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testGroupProtectedStatic 1 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupProtectedStatic 2 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupProtectedStatic 3 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 1 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 2 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 3 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 4 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 5 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 6 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 7 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testMessyNullableType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?array',
'nullable_type' => true,
],
],
[
'/* testNamespaceType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '\MyNamespace\MyClass',
'nullable_type' => false,
],
],
[
'/* testNullableNamespaceType 1 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?ClassName',
'nullable_type' => true,
],
],
[
'/* testNullableNamespaceType 2 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?Folder\ClassName',
'nullable_type' => true,
],
],
[
'/* testMultilineNamespaceType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '\MyNamespace\MyClass\Foo',
'nullable_type' => false,
],
],
[
'/* testPropertyAfterMethod */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testInterfaceProperty */',
[],
],
[
'/* testNestedProperty 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testNestedProperty 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPHP8MixedTypeHint */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'is_readonly' => false,
'type' => 'miXed',
'nullable_type' => false,
],
],
[
'/* testPHP8MixedTypeHintNullable */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?mixed',
'nullable_type' => true,
],
],
[
'/* testNamespaceOperatorTypeHint */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?namespace\Name',
'nullable_type' => true,
],
],
[
'/* testPHP8UnionTypesSimple */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'int|float',
'nullable_type' => false,
],
],
[
'/* testPHP8UnionTypesTwoClasses */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'MyClassA|\Package\MyClassB',
'nullable_type' => false,
],
],
[
'/* testPHP8UnionTypesAllBaseTypes */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'array|bool|int|float|NULL|object|string',
'nullable_type' => false,
],
],
[
'/* testPHP8UnionTypesAllPseudoTypes */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'is_readonly' => false,
'type' => 'false|mixed|self|parent|iterable|Resource',
'nullable_type' => false,
],
],
[
'/* testPHP8UnionTypesIllegalTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
// Missing static, but that's OK as not an allowed syntax.
'type' => 'callable||void',
'nullable_type' => false,
],
],
[
'/* testPHP8UnionTypesNullable */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?int|float',
'nullable_type' => true,
],
],
[
'/* testPHP8PseudoTypeNull */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'null',
'nullable_type' => false,
],
],
[
'/* testPHP8PseudoTypeFalse */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'false',
'nullable_type' => false,
],
],
[
'/* testPHP8PseudoTypeFalseAndBool */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'bool|FALSE',
'nullable_type' => false,
],
],
[
'/* testPHP8ObjectAndClass */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'object|ClassName',
'nullable_type' => false,
],
],
[
'/* testPHP8PseudoTypeIterableAndArray */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'iterable|array|Traversable',
'nullable_type' => false,
],
],
[
'/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'int|string|INT',
'nullable_type' => false,
],
],
[
'/* testPHP81Readonly */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => true,
'type' => 'int',
'nullable_type' => false,
],
],
[
'/* testPHP81ReadonlyWithNullableType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => true,
'type' => '?array',
'nullable_type' => true,
],
],
[
'/* testPHP81ReadonlyWithUnionType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => true,
'type' => 'string|int',
'nullable_type' => false,
],
],
[
'/* testPHP81ReadonlyWithUnionTypeWithNull */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => true,
'type' => 'string|null',
'nullable_type' => false,
],
],
[
'/* testPHP81OnlyReadonlyWithUnionType */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'is_readonly' => true,
'type' => 'string|int',
'nullable_type' => false,
],
],
[
'/* testPHP8PropertySingleAttribute */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'string',
'nullable_type' => false,
],
],
[
'/* testPHP8PropertyMultipleAttributes */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => '?int|float',
'nullable_type' => true,
],
],
[
'/* testPHP8PropertyMultilineAttribute */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'is_readonly' => false,
'type' => 'mixed',
'nullable_type' => false,
],
],
[
'/* testEnumProperty */',
[],
],
[
'/* testPHP81IntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'Foo&Bar',
'nullable_type' => false,
],
],
[
'/* testPHP81MoreIntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'Foo&Bar&Baz',
'nullable_type' => false,
],
],
[
'/* testPHP81IllegalIntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'int&string',
'nullable_type' => false,
],
],
[
'/* testPHP81NulltableIntersectionType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '?Foo&Bar',
'nullable_type' => true,
],
],
];
}//end dataGetMemberProperties()
/**
* Test receiving an expected exception when a non property is passed.
*
* @param string $identifier Comment which precedes the test case.
*
* @expectedException PHP_CodeSniffer\Exceptions\RuntimeException
* @expectedExceptionMessage $stackPtr is not a class member var
*
* @dataProvider dataNotClassProperty
*
* @return void
*/
public function testNotClassPropertyException($identifier)
{
$variable = $this->getTargetToken($identifier, T_VARIABLE);
$result = self::$phpcsFile->getMemberProperties($variable);
}//end testNotClassPropertyException()
/**
* Data provider for the NotClassPropertyException test.
*
* @see testNotClassPropertyException()
*
* @return array
*/
public function dataNotClassProperty()
{
return [
['/* testMethodParam */'],
['/* testImportedGlobal */'],
['/* testLocalVariable */'],
['/* testGlobalVariable */'],
['/* testNestedMethodParam 1 */'],
['/* testNestedMethodParam 2 */'],
['/* testEnumMethodParamNotProperty */'],
];
}//end dataNotClassProperty()
/**
* Test receiving an expected exception when a non variable is passed.
*
* @expectedException PHP_CodeSniffer\Exceptions\RuntimeException
* @expectedExceptionMessage $stackPtr must be of type T_VARIABLE
*
* @return void
*/
public function testNotAVariableException()
{
$next = $this->getTargetToken('/* testNotAVariable */', T_RETURN);
$result = self::$phpcsFile->getMemberProperties($next);
}//end testNotAVariableException()
}//end class
@@ -0,0 +1,164 @@
<?php
/* testPassByReference */
function passByReference(&$var) {}
/* testArrayHint */
function arrayHint(array $var) {}
/* testVariable */
function variable($var) {}
/* testSingleDefaultValue */
function defaultValue($var1=self::CONSTANT) {}
/* testDefaultValues */
function defaultValues($var1=1, $var2='value') {}
/* testTypeHint */
function typeHint(foo $var1, bar $var2) {}
class MyClass {
/* testSelfTypeHint */
function typeSelfHint(self $var) {}
}
/* testNullableTypeHint */
function nullableTypeHint(?int $var1, ?\bar $var2) {}
/* testBitwiseAndConstantExpressionDefaultValue */
function myFunction($a = 10 & 20) {}
/* testArrowFunction */
fn(int $a, ...$b) => $b;
/* testPHP8MixedTypeHint */
function mixedTypeHint(mixed &...$var1) {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(?Mixed $var1) {}
/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint(?namespace\Name $var1) {}
/* testPHP8UnionTypesSimple */
function unionTypeSimple(int|float $number, self|parent &...$obj) {}
/* testPHP8UnionTypesWithSpreadOperatorAndReference */
function globalFunctionWithSpreadAndReference(float|null &$paramA, string|int ...$paramB) {}
/* testPHP8UnionTypesSimpleWithBitwiseOrInDefault */
$fn = fn(int|float $var = CONSTANT_A | CONSTANT_B) => $var;
/* testPHP8UnionTypesTwoClasses */
function unionTypesTwoClasses(MyClassA|\Package\MyClassB $var) {}
/* testPHP8UnionTypesAllBaseTypes */
function unionTypesAllBaseTypes(array|bool|callable|int|float|null|object|string $var) {}
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var) {}
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
$closure = function (?int|float $number) {};
/* testPHP8PseudoTypeNull */
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeNull(null $var = null) {}
/* testPHP8PseudoTypeFalse */
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeFalse(false $var = false) {}
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
function pseudoTypeFalseAndBool(bool|false $var = false) {}
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
function objectAndClass(object|ClassName $var) {}
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}
class ConstructorPropertyPromotionNoTypes {
/* testPHP8ConstructorPropertyPromotionNoTypes */
public function __construct(
public $x = 0.0,
protected $y = '',
private $z = null,
) {}
}
class ConstructorPropertyPromotionWithTypes {
/* testPHP8ConstructorPropertyPromotionWithTypes */
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
}
class ConstructorPropertyPromotionAndNormalParams {
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
public function __construct(public int $promotedProp, ?int $normalArg) {}
}
class ConstructorPropertyPromotionWithReadOnly {
/* testPHP81ConstructorPropertyPromotionWithReadOnly */
public function __construct(public readonly ?int $promotedProp, readonly private string|bool &$promotedToo) {}
}
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
function globalFunction(private $x) {}
abstract class ConstructorPropertyPromotionAbstractMethod {
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
// Intentional fatal error.
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
// 3. The callable type is not supported for properties, but that's not the concern of this method.
abstract public function __construct(public callable $y, private ...$x);
}
/* testCommentsInParameter */
function commentsInParams(
// Leading comment.
?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ 'default value' . /*-*/ 'second part' // Trailing comment.
) {}
/* testParameterAttributesInFunctionDeclaration */
class ParametersWithAttributes(
public function __construct(
#[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute,
#[MyAttr([1, 2])]
Type|false
$typedParamSingleAttribute,
#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute,
#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes,
#[MyAttribute(array("key" => "value"))]
&...$otherParam,
) {}
}
/* testPHP8IntersectionTypes */
function intersectionTypes(Foo&Bar $obj1, Boo&Bar $obj2) {}
/* testPHP81IntersectionTypesWithSpreadOperatorAndReference */
function globalFunctionWithSpreadAndReference(Boo&Bar &$paramA, Foo&Bar ...$paramB) {}
/* testPHP81MoreIntersectionTypes */
function moreIntersectionTypes(MyClassA&\Package\MyClassB&\Package\MyClassC $var) {}
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
$closure = function (string&int $numeric_string) {};
/* testPHP81NullableIntersectionTypes */
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (?Foo&Bar $object) {};
@@ -0,0 +1,152 @@
<?php
/* testBasicFunction */
function myFunction() {}
/* testReturnFunction */
function myFunction(array ...$arrays): array
{
/* testNestedClosure */
return array_map(function(array $array): int {
return array_sum($array);
}, $arrays);
}
class MyClass {
/* testBasicMethod */
function myFunction() {}
/* testPrivateStaticMethod */
private static function myFunction() {}
/* testFinalMethod */
final public function myFunction() {}
/* testProtectedReturnMethod */
protected function myFunction() : int {}
/* testPublicReturnMethod */
public function myFunction(): array {}
/* testNullableReturnMethod */
public function myFunction(): ?array {}
/* testMessyNullableReturnMethod */
public function myFunction() /* comment
*/ :
/* comment */ ? //comment
array {}
/* testReturnNamespace */
function myFunction(): \MyNamespace\MyClass {}
/* testReturnMultilineNamespace */
function myFunction(): \MyNamespace /** comment *\/ comment */
\MyClass /* comment */
\Foo {}
}
abstract class MyClass
{
/* testAbstractMethod */
abstract function myFunction();
/* testAbstractReturnMethod */
abstract protected function myFunction(): bool;
}
interface MyInterface
{
/* testInterfaceMethod */
function myFunction();
}
$result = array_map(
/* testArrowFunction */
static fn(int $number) : int => $number + 1,
$numbers
);
class ReturnMe {
/* testReturnTypeStatic */
private function myFunction(): static {
return $this;
}
}
/* testPHP8MixedTypeHint */
function mixedTypeHint() :mixed {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(): ?mixed {}
/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint() : ?namespace\Name {}
/* testPHP8UnionTypesSimple */
function unionTypeSimple($number) : int|float {}
/* testPHP8UnionTypesTwoClasses */
$fn = fn($var): MyClassA|\Package\MyClassB => $var;
/* testPHP8UnionTypesAllBaseTypes */
function unionTypesAllBaseTypes() : array|bool|callable|int|float|null|Object|string {}
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterable|Resource|void {}
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
$closure = function () use($a) :?int|float {};
/* testPHP8PseudoTypeNull */
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeNull(): null {}
/* testPHP8PseudoTypeFalse */
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeFalse(): false {}
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
function pseudoTypeFalseAndBool(): bool|false {}
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
function objectAndClass(): object|ClassName {}
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
interface FooBar {
public function pseudoTypeIterableAndArray(): iterable|array|Traversable;
}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion(): int | /*comment*/ string | INT {}
/* testPHP81NeverType */
function never(): never {}
/* testPHP81NullableNeverType */
// Intentional fatal error - nullability is not allowed with never, but that's not the concern of the method.
function nullableNever(): ?never {}
/* testPHP8IntersectionTypes */
function intersectionTypes(): Foo&Bar {}
/* testPHP81MoreIntersectionTypes */
function moreIntersectionTypes(): MyClassA&\Package\MyClassB&\Package\MyClassC {}
/* testPHP81IntersectionArrowFunction */
$fn = fn($var): MyClassA&\Package\MyClassB => $var;
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
$closure = function (): string&int {};
/* testPHP81NullableIntersectionTypes */
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (): ?Foo&Bar {};
@@ -0,0 +1,910 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:getMethodProperties method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class GetMethodPropertiesTest extends AbstractMethodUnitTest
{
/**
* Test a basic function.
*
* @return void
*/
public function testBasicFunction()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testBasicFunction()
/**
* Test a function with a return type.
*
* @return void
*/
public function testReturnFunction()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'array',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testReturnFunction()
/**
* Test a closure used as a function argument.
*
* @return void
*/
public function testNestedClosure()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'int',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testNestedClosure()
/**
* Test a basic method.
*
* @return void
*/
public function testBasicMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testBasicMethod()
/**
* Test a private static method.
*
* @return void
*/
public function testPrivateStaticMethod()
{
$expected = [
'scope' => 'private',
'scope_specified' => true,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => true,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPrivateStaticMethod()
/**
* Test a basic final method.
*
* @return void
*/
public function testFinalMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => true,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => true,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testFinalMethod()
/**
* Test a protected method with a return type.
*
* @return void
*/
public function testProtectedReturnMethod()
{
$expected = [
'scope' => 'protected',
'scope_specified' => true,
'return_type' => 'int',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testProtectedReturnMethod()
/**
* Test a public method with a return type.
*
* @return void
*/
public function testPublicReturnMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => true,
'return_type' => 'array',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPublicReturnMethod()
/**
* Test a public method with a nullable return type.
*
* @return void
*/
public function testNullableReturnMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => true,
'return_type' => '?array',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testNullableReturnMethod()
/**
* Test a public method with a nullable return type.
*
* @return void
*/
public function testMessyNullableReturnMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => true,
'return_type' => '?array',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testMessyNullableReturnMethod()
/**
* Test a method with a namespaced return type.
*
* @return void
*/
public function testReturnNamespace()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '\MyNamespace\MyClass',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testReturnNamespace()
/**
* Test a method with a messy namespaces return type.
*
* @return void
*/
public function testReturnMultilineNamespace()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '\MyNamespace\MyClass\Foo',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testReturnMultilineNamespace()
/**
* Test a basic abstract method.
*
* @return void
*/
public function testAbstractMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => true,
'is_final' => false,
'is_static' => false,
'has_body' => false,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testAbstractMethod()
/**
* Test an abstract method with a return type.
*
* @return void
*/
public function testAbstractReturnMethod()
{
$expected = [
'scope' => 'protected',
'scope_specified' => true,
'return_type' => 'bool',
'nullable_return_type' => false,
'is_abstract' => true,
'is_final' => false,
'is_static' => false,
'has_body' => false,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testAbstractReturnMethod()
/**
* Test a basic interface method.
*
* @return void
*/
public function testInterfaceMethod()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => false,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testInterfaceMethod()
/**
* Test a static arrow function.
*
* @return void
*/
public function testArrowFunction()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'int',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => true,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testArrowFunction()
/**
* Test a function with return type "static".
*
* @return void
*/
public function testReturnTypeStatic()
{
$expected = [
'scope' => 'private',
'scope_specified' => true,
'return_type' => 'static',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testReturnTypeStatic()
/**
* Test a function with return type "mixed".
*
* @return void
*/
public function testPHP8MixedTypeHint()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'mixed',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8MixedTypeHint()
/**
* Test a function with return type "mixed" and nullability.
*
* @return void
*/
public function testPHP8MixedTypeHintNullable()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?mixed',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8MixedTypeHintNullable()
/**
* Test a function with return type using the namespace operator.
*
* @return void
*/
public function testNamespaceOperatorTypeHint()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?namespace\Name',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testNamespaceOperatorTypeHint()
/**
* Verify recognition of PHP8 union type declaration.
*
* @return void
*/
public function testPHP8UnionTypesSimple()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'int|float',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8UnionTypesSimple()
/**
* Verify recognition of PHP8 union type declaration with two classes.
*
* @return void
*/
public function testPHP8UnionTypesTwoClasses()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'MyClassA|\Package\MyClassB',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8UnionTypesTwoClasses()
/**
* Verify recognition of PHP8 union type declaration with all base types.
*
* @return void
*/
public function testPHP8UnionTypesAllBaseTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'array|bool|callable|int|float|null|Object|string',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8UnionTypesAllBaseTypes()
/**
* Verify recognition of PHP8 union type declaration with all pseudo types.
*
* @return void
*/
public function testPHP8UnionTypesAllPseudoTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'false|MIXED|self|parent|static|iterable|Resource|void',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8UnionTypesAllPseudoTypes()
/**
* Verify recognition of PHP8 union type declaration with (illegal) nullability.
*
* @return void
*/
public function testPHP8UnionTypesNullable()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?int|float',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8UnionTypesNullable()
/**
* Verify recognition of PHP8 type declaration with (illegal) single type null.
*
* @return void
*/
public function testPHP8PseudoTypeNull()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'null',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8PseudoTypeNull()
/**
* Verify recognition of PHP8 type declaration with (illegal) single type false.
*
* @return void
*/
public function testPHP8PseudoTypeFalse()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'false',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8PseudoTypeFalse()
/**
* Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool.
*
* @return void
*/
public function testPHP8PseudoTypeFalseAndBool()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'bool|false',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8PseudoTypeFalseAndBool()
/**
* Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name.
*
* @return void
*/
public function testPHP8ObjectAndClass()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'object|ClassName',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8ObjectAndClass()
/**
* Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable.
*
* @return void
*/
public function testPHP8PseudoTypeIterableAndArray()
{
$expected = [
'scope' => 'public',
'scope_specified' => true,
'return_type' => 'iterable|array|Traversable',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => false,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8PseudoTypeIterableAndArray()
/**
* Verify recognition of PHP8 type declaration with (illegal) duplicate types.
*
* @return void
*/
public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'int|string|INT',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()
/**
* Verify recognition of PHP8.1 type "never".
*
* @return void
*/
public function testPHP81NeverType()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'never',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81NeverType()
/**
* Verify recognition of PHP8.1 type "never" with (illegal) nullability.
*
* @return void
*/
public function testPHP81NullableNeverType()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?never',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81NullableNeverType()
/**
* Verify recognition of PHP8.1 intersection type declaration.
*
* @return void
*/
public function testPHP8IntersectionTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'Foo&Bar',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8IntersectionTypes()
/**
* Verify recognition of PHP8.1 intersection type declaration with more types.
*
* @return void
*/
public function testPHP81MoreIntersectionTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'MyClassA&\Package\MyClassB&\Package\MyClassC',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81MoreIntersectionTypes()
/**
* Verify recognition of PHP8.1 intersection type declaration in arrow function.
*
* @return void
*/
public function testPHP81IntersectionArrowFunction()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'MyClassA&\Package\MyClassB',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81IntersectionArrowFunction()
/**
* Verify recognition of PHP8.1 intersection type declaration with illegal simple types.
*
* @return void
*/
public function testPHP81IllegalIntersectionTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => 'string&int',
'nullable_return_type' => false,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81IllegalIntersectionTypes()
/**
* Verify recognition of PHP8.1 intersection type declaration with (illegal) nullability.
*
* @return void
*/
public function testPHP81NullableIntersectionTypes()
{
$expected = [
'scope' => 'public',
'scope_specified' => false,
'return_type' => '?Foo&Bar',
'nullable_return_type' => true,
'is_abstract' => false,
'is_final' => false,
'is_static' => false,
'has_body' => true,
];
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP81NullableIntersectionTypes()
/**
* Test helper.
*
* @param string $commentString The comment which preceeds the test.
* @param array $expected The expected function output.
*
* @return void
*/
private function getMethodPropertiesTestHelper($commentString, $expected)
{
$function = $this->getTargetToken($commentString, [T_FUNCTION, T_CLOSURE, T_FN]);
$found = self::$phpcsFile->getMethodProperties($function);
$this->assertArraySubset($expected, $found, true);
}//end getMethodPropertiesTestHelper()
}//end class
@@ -0,0 +1,150 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\File;
/* testBitwiseAndA */
error_reporting( E_NOTICE & E_STRICT );
/* testBitwiseAndB */
$a = [ $something & $somethingElse ];
/* testBitwiseAndC */
$a = [ $first, $something & self::$somethingElse ];
/* testBitwiseAndD */
$a = array( $first, $something & $somethingElse );
/* testBitwiseAndE */
$a = [ 'a' => $first, 'b' => $something & $somethingElse ];
/* testBitwiseAndF */
$a = array( 'a' => $first, 'b' => $something & \MyClass::$somethingElse );
/* testBitwiseAndG */
$a = $something & $somethingElse;
/* testBitwiseAndH */
function myFunction($a = 10 & 20) {}
/* testBitwiseAndI */
$closure = function ($a = MY_CONSTANT & parent::OTHER_CONSTANT) {};
/* testFunctionReturnByReference */
function &myFunction() {}
/* testFunctionPassByReferenceA */
function myFunction( &$a ) {}
/* testFunctionPassByReferenceB */
function myFunction( $a, &$b ) {}
/* testFunctionPassByReferenceC */
$closure = function ( &$a ) {};
/* testFunctionPassByReferenceD */
$closure = function ( $a, &$b ) {};
/* testFunctionPassByReferenceE */
function myFunction(array &$one) {}
/* testFunctionPassByReferenceF */
$closure = function (\MyClass &$one) {};
/* testFunctionPassByReferenceG */
$closure = function ($param, &...$moreParams) {};
/* testForeachValueByReference */
foreach( $array as $key => &$value ) {}
/* testForeachKeyByReference */
foreach( $array as &$key => $value ) {}
/* testArrayValueByReferenceA */
$a = [ 'a' => &$something ];
/* testArrayValueByReferenceB */
$a = [ 'a' => $something, 'b' => &$somethingElse ];
/* testArrayValueByReferenceC */
$a = [ &$something ];
/* testArrayValueByReferenceD */
$a = [ $something, &$somethingElse ];
/* testArrayValueByReferenceE */
$a = array( 'a' => &$something );
/* testArrayValueByReferenceF */
$a = array( 'a' => $something, 'b' => &$somethingElse );
/* testArrayValueByReferenceG */
$a = array( &$something );
/* testArrayValueByReferenceH */
$a = array( $something, &$somethingElse );
/* testAssignByReferenceA */
$b = &$something;
/* testAssignByReferenceB */
$b =& $something;
/* testAssignByReferenceC */
$b .= &$something;
/* testAssignByReferenceD */
$myValue = &$obj->getValue();
/* testAssignByReferenceE */
$collection = &collector();
/* testPassByReferenceA */
functionCall(&$something, $somethingElse);
/* testPassByReferenceB */
functionCall($something, &$somethingElse);
/* testPassByReferenceC */
functionCall($something, &$this->somethingElse);
/* testPassByReferenceD */
functionCall($something, &self::$somethingElse);
/* testPassByReferenceE */
functionCall($something, &parent::$somethingElse);
/* testPassByReferenceF */
functionCall($something, &static::$somethingElse);
/* testPassByReferenceG */
functionCall($something, &SomeClass::$somethingElse);
/* testPassByReferenceH */
functionCall(&\SomeClass::$somethingElse);
/* testPassByReferenceI */
functionCall($something, &\SomeNS\SomeClass::$somethingElse);
/* testPassByReferenceJ */
functionCall($something, &namespace\SomeClass::$somethingElse);
/* testNewByReferenceA */
$foobar2 = &new Foobar();
/* testNewByReferenceB */
functionCall( $something , &new Foobar() );
/* testUseByReference */
$closure = function() use (&$var){};
/* testArrowFunctionReturnByReference */
fn&($x) => $x;
/* testArrowFunctionPassByReferenceA */
$fn = fn(array &$one) => 1;
/* testArrowFunctionPassByReferenceB */
$fn = fn($param, &...$moreParams) => 1;
/* testClosureReturnByReference */
$closure = function &($param) use ($value) {};
@@ -0,0 +1,248 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:isReference method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class IsReferenceTest extends AbstractMethodUnitTest
{
/**
* Test correctly identifying whether a "bitwise and" token is a reference or not.
*
* @param string $identifier Comment which precedes the test case.
* @param bool $expected Expected function output.
*
* @dataProvider dataIsReference
*
* @return void
*/
public function testIsReference($identifier, $expected)
{
$bitwiseAnd = $this->getTargetToken($identifier, T_BITWISE_AND);
$result = self::$phpcsFile->isReference($bitwiseAnd);
$this->assertSame($expected, $result);
}//end testIsReference()
/**
* Data provider for the IsReference test.
*
* @see testIsReference()
*
* @return array
*/
public function dataIsReference()
{
return [
[
'/* testBitwiseAndA */',
false,
],
[
'/* testBitwiseAndB */',
false,
],
[
'/* testBitwiseAndC */',
false,
],
[
'/* testBitwiseAndD */',
false,
],
[
'/* testBitwiseAndE */',
false,
],
[
'/* testBitwiseAndF */',
false,
],
[
'/* testBitwiseAndG */',
false,
],
[
'/* testBitwiseAndH */',
false,
],
[
'/* testBitwiseAndI */',
false,
],
[
'/* testFunctionReturnByReference */',
true,
],
[
'/* testFunctionPassByReferenceA */',
true,
],
[
'/* testFunctionPassByReferenceB */',
true,
],
[
'/* testFunctionPassByReferenceC */',
true,
],
[
'/* testFunctionPassByReferenceD */',
true,
],
[
'/* testFunctionPassByReferenceE */',
true,
],
[
'/* testFunctionPassByReferenceF */',
true,
],
[
'/* testFunctionPassByReferenceG */',
true,
],
[
'/* testForeachValueByReference */',
true,
],
[
'/* testForeachKeyByReference */',
true,
],
[
'/* testArrayValueByReferenceA */',
true,
],
[
'/* testArrayValueByReferenceB */',
true,
],
[
'/* testArrayValueByReferenceC */',
true,
],
[
'/* testArrayValueByReferenceD */',
true,
],
[
'/* testArrayValueByReferenceE */',
true,
],
[
'/* testArrayValueByReferenceF */',
true,
],
[
'/* testArrayValueByReferenceG */',
true,
],
[
'/* testArrayValueByReferenceH */',
true,
],
[
'/* testAssignByReferenceA */',
true,
],
[
'/* testAssignByReferenceB */',
true,
],
[
'/* testAssignByReferenceC */',
true,
],
[
'/* testAssignByReferenceD */',
true,
],
[
'/* testAssignByReferenceE */',
true,
],
[
'/* testPassByReferenceA */',
true,
],
[
'/* testPassByReferenceB */',
true,
],
[
'/* testPassByReferenceC */',
true,
],
[
'/* testPassByReferenceD */',
true,
],
[
'/* testPassByReferenceE */',
true,
],
[
'/* testPassByReferenceF */',
true,
],
[
'/* testPassByReferenceG */',
true,
],
[
'/* testPassByReferenceH */',
true,
],
[
'/* testPassByReferenceI */',
true,
],
[
'/* testPassByReferenceJ */',
true,
],
[
'/* testNewByReferenceA */',
true,
],
[
'/* testNewByReferenceB */',
true,
],
[
'/* testUseByReference */',
true,
],
[
'/* testArrowFunctionReturnByReference */',
true,
],
[
'/* testArrowFunctionPassByReferenceA */',
true,
],
[
'/* testArrowFunctionPassByReferenceB */',
true,
],
[
'/* testClosureReturnByReference */',
true,
],
];
}//end dataIsReference()
}//end class
@@ -0,0 +1,154 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Filters\Filter::accept method.
*
* @author Willington Vega <wvega@wvega.com>
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 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\Filters\Filter;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Filters\Filter;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\TestCase;
class AcceptTest extends TestCase
{
/**
* The Config object.
*
* @var \PHP_CodeSniffer\Config
*/
protected static $config;
/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected static $ruleset;
/**
* Initialize the test.
*
* @return void
*/
public function setUp()
{
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// PEAR installs test and sniff files into different locations
// so these tests will not pass as they directly reference files
// by relative location.
$this->markTestSkipped('Test cannot run from a PEAR install');
}
}//end setUp()
/**
* Initialize the config and ruleset objects based on the `AcceptTest.xml` ruleset file.
*
* @return void
*/
public static function setUpBeforeClass()
{
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// This test will be skipped.
return;
}
$standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml';
self::$config = new Config(["--standard=$standard", "--ignore=*/somethingelse/*"]);
self::$ruleset = new Ruleset(self::$config);
}//end setUpBeforeClass()
/**
* Test filtering a file list for excluded paths.
*
* @param array $inputPaths List of file paths to be filtered.
* @param array $expectedOutput Expected filtering result.
*
* @dataProvider dataExcludePatterns
*
* @return void
*/
public function testExcludePatterns($inputPaths, $expectedOutput)
{
$fakeDI = new \RecursiveArrayIterator($inputPaths);
$filter = new Filter($fakeDI, '/', self::$config, self::$ruleset);
$iterator = new \RecursiveIteratorIterator($filter);
$files = [];
foreach ($iterator as $file) {
$files[] = $file;
}
$this->assertEquals($expectedOutput, $files);
}//end testExcludePatterns()
/**
* Data provider.
*
* @see testExcludePatterns
*
* @return array
*/
public function dataExcludePatterns()
{
$testCases = [
// Test top-level exclude patterns.
[
[
'/path/to/src/Main.php',
'/path/to/src/Something/Main.php',
'/path/to/src/Somethingelse/Main.php',
'/path/to/src/SomethingelseEvenLonger/Main.php',
'/path/to/src/Other/Main.php',
],
[
'/path/to/src/Main.php',
'/path/to/src/SomethingelseEvenLonger/Main.php',
],
],
// Test ignoring standard/sniff specific exclude patterns.
[
[
'/path/to/src/generic-project/Main.php',
'/path/to/src/generic/Main.php',
'/path/to/src/anything-generic/Main.php',
],
[
'/path/to/src/generic-project/Main.php',
'/path/to/src/generic/Main.php',
'/path/to/src/anything-generic/Main.php',
],
],
];
// Allow these tests to work on Windows as well.
if (DIRECTORY_SEPARATOR === '\\') {
foreach ($testCases as $key => $case) {
foreach ($case as $nr => $param) {
foreach ($param as $file => $value) {
$testCases[$key][$nr][$file] = strtr($value, '/', '\\');
}
}
}
}
return $testCases;
}//end dataExcludePatterns()
}//end class
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="AcceptTest" xsi:noNamespaceSchemaLocation="phpcs.xsd">
<description>Ruleset to test the filtering based on exclude patterns.</description>
<!-- Directory pattern. -->
<exclude-pattern>*/something/*</exclude-pattern>
<!-- File pattern. -->
<exclude-pattern>*/Other/Main\.php$</exclude-pattern>
<rule ref="Generic">
<!-- Standard specific directory pattern. -->
<exclude-pattern>/anything/*</exclude-pattern>
<!-- Standard specific file pattern. -->
<exclude-pattern>/YetAnother/Main\.php</exclude-pattern>
</rule>
</ruleset>
@@ -0,0 +1,135 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Util\Common::isCamelCaps method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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;
use PHP_CodeSniffer\Util\Common;
use PHPUnit\Framework\TestCase;
class IsCamelCapsTest extends TestCase
{
/**
* Test valid public function/method names.
*
* @return void
*/
public function testValidNotClassFormatPublic()
{
$this->assertTrue(Common::isCamelCaps('thisIsCamelCaps', false, true, true));
$this->assertTrue(Common::isCamelCaps('thisISCamelCaps', false, true, false));
}//end testValidNotClassFormatPublic()
/**
* Test invalid public function/method names.
*
* @return void
*/
public function testInvalidNotClassFormatPublic()
{
$this->assertFalse(Common::isCamelCaps('_thisIsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('thisISCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('ThisIsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('3thisIsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('*thisIsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('-thisIsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('this*IsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('this-IsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('this_IsCamelCaps', false, true, true));
$this->assertFalse(Common::isCamelCaps('this_is_camel_caps', false, true, true));
}//end testInvalidNotClassFormatPublic()
/**
* Test valid private method names.
*
* @return void
*/
public function testValidNotClassFormatPrivate()
{
$this->assertTrue(Common::isCamelCaps('_thisIsCamelCaps', false, false, true));
$this->assertTrue(Common::isCamelCaps('_thisISCamelCaps', false, false, false));
$this->assertTrue(Common::isCamelCaps('_i18N', false, false, true));
$this->assertTrue(Common::isCamelCaps('_i18n', false, false, true));
}//end testValidNotClassFormatPrivate()
/**
* Test invalid private method names.
*
* @return void
*/
public function testInvalidNotClassFormatPrivate()
{
$this->assertFalse(Common::isCamelCaps('thisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('_thisISCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('__thisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('__thisISCamelCaps', false, false, false));
$this->assertFalse(Common::isCamelCaps('3thisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('*thisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('-thisIsCamelCaps', false, false, true));
$this->assertFalse(Common::isCamelCaps('_this_is_camel_caps', false, false, true));
}//end testInvalidNotClassFormatPrivate()
/**
* Test valid class names.
*
* @return void
*/
public function testValidClassFormatPublic()
{
$this->assertTrue(Common::isCamelCaps('ThisIsCamelCaps', true, true, true));
$this->assertTrue(Common::isCamelCaps('ThisISCamelCaps', true, true, false));
$this->assertTrue(Common::isCamelCaps('This3IsCamelCaps', true, true, false));
}//end testValidClassFormatPublic()
/**
* Test invalid class names.
*
* @return void
*/
public function testInvalidClassFormat()
{
$this->assertFalse(Common::isCamelCaps('thisIsCamelCaps', true));
$this->assertFalse(Common::isCamelCaps('This-IsCamelCaps', true));
$this->assertFalse(Common::isCamelCaps('This_Is_Camel_Caps', true));
}//end testInvalidClassFormat()
/**
* Test invalid class names with the private flag set.
*
* Note that the private flag is ignored if the class format
* flag is set, so these names are all invalid.
*
* @return void
*/
public function testInvalidClassFormatPrivate()
{
$this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', true, true));
$this->assertFalse(Common::isCamelCaps('_ThisIsCamelCaps', true, false));
}//end testInvalidClassFormatPrivate()
}//end class
@@ -0,0 +1,118 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Ruleset class using a Linux-style absolute path to include a sniff.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Ruleset;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\TestCase;
class RuleInclusionAbsoluteLinuxTest extends TestCase
{
/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected $ruleset;
/**
* Path to the ruleset file.
*
* @var string
*/
private $standard = '';
/**
* The original content of the ruleset.
*
* @var string
*/
private $contents = '';
/**
* Initialize the config and ruleset objects.
*
* @return void
*/
public function setUp()
{
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// PEAR installs test and sniff files into different locations
// so these tests will not pass as they directly reference files
// by relative location.
$this->markTestSkipped('Test cannot run from a PEAR install');
}
$this->standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml';
$repoRootDir = dirname(dirname(dirname(__DIR__)));
// On-the-fly adjust the ruleset test file to be able to test sniffs included with absolute paths.
$contents = file_get_contents($this->standard);
$this->contents = $contents;
$newPath = $repoRootDir;
if (DIRECTORY_SEPARATOR === '\\') {
$newPath = str_replace('\\', '/', $repoRootDir);
}
$adjusted = str_replace('%path_slash_forward%', $newPath, $contents);
if (file_put_contents($this->standard, $adjusted) === false) {
$this->markTestSkipped('On the fly ruleset adjustment failed');
}
// Initialize the config and ruleset objects for the test.
$config = new Config(["--standard={$this->standard}"]);
$this->ruleset = new Ruleset($config);
}//end setUp()
/**
* Reset ruleset file.
*
* @return void
*/
public function tearDown()
{
file_put_contents($this->standard, $this->contents);
}//end tearDown()
/**
* Test that sniffs registed with a Linux absolute path are correctly recognized and that
* properties are correctly set for them.
*
* @return void
*/
public function testLinuxStylePathRuleInclusion()
{
// Test that the sniff is correctly registered.
$this->assertObjectHasAttribute('sniffCodes', $this->ruleset);
$this->assertCount(1, $this->ruleset->sniffCodes);
$this->assertArrayHasKey('Generic.Formatting.SpaceAfterNot', $this->ruleset->sniffCodes);
$this->assertSame(
'PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\SpaceAfterNotSniff',
$this->ruleset->sniffCodes['Generic.Formatting.SpaceAfterNot']
);
// Test that the sniff properties are correctly set.
$this->assertSame(
'10',
$this->ruleset->sniffs['PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\SpaceAfterNotSniff']->spacing
);
}//end testLinuxStylePathRuleInclusion()
}//end class
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RuleInclusionAbsoluteLinuxTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/squizlabs/PHP_CodeSniffer/master/phpcs.xsd">
<!-- %path_slash_forward% will be replaced on the fly -->
<rule ref="%path_slash_forward%/src/Standards/Generic/Sniffs/Formatting/SpaceAfterNotSniff.php">
<properties>
<property name="spacing" value="10" />
</properties>
</rule>
</ruleset>
@@ -0,0 +1,119 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Ruleset class using a Windows-style absolute path to include a sniff.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Ruleset;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\TestCase;
class RuleInclusionAbsoluteWindowsTest extends TestCase
{
/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected $ruleset;
/**
* Path to the ruleset file.
*
* @var string
*/
private $standard = '';
/**
* The original content of the ruleset.
*
* @var string
*/
private $contents = '';
/**
* Initialize the config and ruleset objects.
*
* @return void
*/
public function setUp()
{
if (DIRECTORY_SEPARATOR === '/') {
$this->markTestSkipped('Windows specific test');
}
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// PEAR installs test and sniff files into different locations
// so these tests will not pass as they directly reference files
// by relative location.
$this->markTestSkipped('Test cannot run from a PEAR install');
}
$this->standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml';
$repoRootDir = dirname(dirname(dirname(__DIR__)));
// On-the-fly adjust the ruleset test file to be able to test sniffs included with absolute paths.
$contents = file_get_contents($this->standard);
$this->contents = $contents;
$adjusted = str_replace('%path_slash_back%', $repoRootDir, $contents);
if (file_put_contents($this->standard, $adjusted) === false) {
$this->markTestSkipped('On the fly ruleset adjustment failed');
}
// Initialize the config and ruleset objects for the test.
$config = new Config(["--standard={$this->standard}"]);
$this->ruleset = new Ruleset($config);
}//end setUp()
/**
* Reset ruleset file.
*
* @return void
*/
public function tearDown()
{
if (DIRECTORY_SEPARATOR !== '/') {
file_put_contents($this->standard, $this->contents);
}
}//end tearDown()
/**
* Test that sniffs registed with a Windows absolute path are correctly recognized and that
* properties are correctly set for them.
*
* @return void
*/
public function testWindowsStylePathRuleInclusion()
{
// Test that the sniff is correctly registered.
$this->assertObjectHasAttribute('sniffCodes', $this->ruleset);
$this->assertCount(1, $this->ruleset->sniffCodes);
$this->assertArrayHasKey('Generic.Formatting.SpaceAfterCast', $this->ruleset->sniffCodes);
$this->assertSame(
'PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\SpaceAfterCastSniff',
$this->ruleset->sniffCodes['Generic.Formatting.SpaceAfterCast']
);
// Test that the sniff property is correctly set.
$this->assertSame(
'10',
$this->ruleset->sniffs['PHP_CodeSniffer\Standards\Generic\Sniffs\Formatting\SpaceAfterCastSniff']->spacing
);
}//end testWindowsStylePathRuleInclusion()
}//end class
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RuleInclusionAbsoluteWindowsTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/squizlabs/PHP_CodeSniffer/master/phpcs.xsd">
<!-- %path_slash_back% will be replaced on the fly -->
<rule ref="%path_slash_back%\src\Standards\Generic\Sniffs\Formatting\SpaceAfterCastSniff.php">
<properties>
<property name="spacing" value="10" />
</properties>
</rule>
</ruleset>
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RuleInclusionTest-include" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/squizlabs/PHP_CodeSniffer/master/phpcs.xsd">
<rule ref="Generic.Metrics.NestingLevel">
<properties>
<property name="nestingLevel" value="2" />
</properties>
</rule>
</ruleset>
@@ -0,0 +1,297 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Ruleset class.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Ruleset;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Ruleset;
use PHPUnit\Framework\TestCase;
class RuleInclusionTest extends TestCase
{
/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected static $ruleset;
/**
* Path to the ruleset file.
*
* @var string
*/
private static $standard = '';
/**
* The original content of the ruleset.
*
* @var string
*/
private static $contents = '';
/**
* Initialize the test.
*
* @return void
*/
public function setUp()
{
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// PEAR installs test and sniff files into different locations
// so these tests will not pass as they directly reference files
// by relative location.
$this->markTestSkipped('Test cannot run from a PEAR install');
}
}//end setUp()
/**
* Initialize the config and ruleset objects based on the `RuleInclusionTest.xml` ruleset file.
*
* @return void
*/
public static function setUpBeforeClass()
{
if ($GLOBALS['PHP_CODESNIFFER_PEAR'] === true) {
// This test will be skipped.
return;
}
$standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml';
self::$standard = $standard;
// On-the-fly adjust the ruleset test file to be able to test
// sniffs included with relative paths.
$contents = file_get_contents($standard);
self::$contents = $contents;
$repoRootDir = basename(dirname(dirname(dirname(__DIR__))));
$newPath = $repoRootDir;
if (DIRECTORY_SEPARATOR === '\\') {
$newPath = str_replace('\\', '/', $repoRootDir);
}
$adjusted = str_replace('%path_root_dir%', $newPath, $contents);
if (file_put_contents($standard, $adjusted) === false) {
self::markTestSkipped('On the fly ruleset adjustment failed');
}
$config = new Config(["--standard=$standard"]);
self::$ruleset = new Ruleset($config);
}//end setUpBeforeClass()
/**
* Reset ruleset file.
*
* @return void
*/
public function tearDown()
{
file_put_contents(self::$standard, self::$contents);
}//end tearDown()
/**
* Test that sniffs are registered.
*
* @return void
*/
public function testHasSniffCodes()
{
$this->assertObjectHasAttribute('sniffCodes', self::$ruleset);
$this->assertCount(14, self::$ruleset->sniffCodes);
}//end testHasSniffCodes()
/**
* Test that sniffs are correctly registered, independently on the syntax used to include the sniff.
*
* @param string $key Expected array key.
* @param string $value Expected array value.
*
* @dataProvider dataRegisteredSniffCodes
*
* @return void
*/
public function testRegisteredSniffCodes($key, $value)
{
$this->assertArrayHasKey($key, self::$ruleset->sniffCodes);
$this->assertSame($value, self::$ruleset->sniffCodes[$key]);
}//end testRegisteredSniffCodes()
/**
* Data provider.
*
* @see self::testRegisteredSniffCodes()
*
* @return array
*/
public function dataRegisteredSniffCodes()
{
return [
[
'PSR1.Classes.ClassDeclaration',
'PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff',
],
[
'PSR1.Files.SideEffects',
'PHP_CodeSniffer\Standards\PSR1\Sniffs\Files\SideEffectsSniff',
],
[
'PSR1.Methods.CamelCapsMethodName',
'PHP_CodeSniffer\Standards\PSR1\Sniffs\Methods\CamelCapsMethodNameSniff',
],
[
'Generic.PHP.DisallowAlternativePHPTags',
'PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\DisallowAlternativePHPTagsSniff',
],
[
'Generic.PHP.DisallowShortOpenTag',
'PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\DisallowShortOpenTagSniff',
],
[
'Generic.Files.ByteOrderMark',
'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\ByteOrderMarkSniff',
],
[
'Squiz.Classes.ValidClassName',
'PHP_CodeSniffer\Standards\Squiz\Sniffs\Classes\ValidClassNameSniff',
],
[
'Generic.NamingConventions.UpperCaseConstantName',
'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\UpperCaseConstantNameSniff',
],
[
'Zend.NamingConventions.ValidVariableName',
'PHP_CodeSniffer\Standards\Zend\Sniffs\NamingConventions\ValidVariableNameSniff',
],
[
'Generic.Arrays.ArrayIndent',
'PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\ArrayIndentSniff',
],
[
'Generic.Metrics.CyclomaticComplexity',
'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff',
],
[
'Generic.Files.LineLength',
'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff',
],
[
'Generic.NamingConventions.CamelCapsFunctionName',
'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff',
],
[
'Generic.Metrics.NestingLevel',
'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff',
],
];
}//end dataRegisteredSniffCodes()
/**
* Test that setting properties for standards, categories, sniffs works for all supported rule
* inclusion methods.
*
* @param string $sniffClass The name of the sniff class.
* @param string $propertyName The name of the changed property.
* @param mixed $expectedValue The value expected for the property.
*
* @dataProvider dataSettingProperties
*
* @return void
*/
public function testSettingProperties($sniffClass, $propertyName, $expectedValue)
{
$this->assertObjectHasAttribute('sniffs', self::$ruleset);
$this->assertArrayHasKey($sniffClass, self::$ruleset->sniffs);
$this->assertObjectHasAttribute($propertyName, self::$ruleset->sniffs[$sniffClass]);
$actualValue = self::$ruleset->sniffs[$sniffClass]->$propertyName;
$this->assertSame($expectedValue, $actualValue);
}//end testSettingProperties()
/**
* Data provider.
*
* @see self::testSettingProperties()
*
* @return array
*/
public function dataSettingProperties()
{
return [
'ClassDeclarationSniff' => [
'PHP_CodeSniffer\Standards\PSR1\Sniffs\Classes\ClassDeclarationSniff',
'setforallsniffs',
true,
],
'SideEffectsSniff' => [
'PHP_CodeSniffer\Standards\PSR1\Sniffs\Files\SideEffectsSniff',
'setforallsniffs',
true,
],
'ValidVariableNameSniff' => [
'PHP_CodeSniffer\Standards\Zend\Sniffs\NamingConventions\ValidVariableNameSniff',
'setforallincategory',
true,
],
'ArrayIndentSniff' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\Arrays\ArrayIndentSniff',
'indent',
'2',
],
'LineLengthSniff' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff',
'lineLimit',
'10',
],
'CamelCapsFunctionNameSniff' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\CamelCapsFunctionNameSniff',
'strict',
false,
],
'NestingLevelSniff-nestingLevel' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff',
'nestingLevel',
'2',
],
'NestingLevelSniff-setforsniffsinincludedruleset' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff',
'setforsniffsinincludedruleset',
true,
],
// Testing that setting a property at error code level does *not* work.
'CyclomaticComplexitySniff' => [
'PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff',
'complexity',
10,
],
];
}//end dataSettingProperties()
}//end class
@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RuleInclusionTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/squizlabs/PHP_CodeSniffer/master/phpcs.xsd">
<rule ref="PSR1">
<properties>
<property name="setforallsniffs" value="true" />
</properties>
</rule>
<rule ref="Zend.NamingConventions">
<properties>
<property name="setforallincategory" value="true" />
</properties>
</rule>
<rule ref="Generic.Arrays.ArrayIndent">
<properties>
<property name="indent" value="2" />
</properties>
</rule>
<rule ref="Generic.Metrics.CyclomaticComplexity.MaxExceeded">
<properties>
<property name="complexity" value="50" />
</properties>
</rule>
<rule ref="./src/Standards/Generic/Sniffs/Files/LineLengthSniff.php">
<properties>
<property name="lineLimit" value="10" />
</properties>
</rule>
<rule ref="./../%path_root_dir%/src/Standards/Generic/Sniffs/NamingConventions/CamelCapsFunctionNameSniff.php">
<properties>
<property name="strict" value="false" />
</properties>
</rule>
<rule ref="./RuleInclusionTest-include.xml">
<properties>
<property name="setforsniffsinincludedruleset" value="true" />
</properties>
</rule>
</ruleset>
@@ -0,0 +1,51 @@
<?php
/* testSimpleValues */
$foo = [1,2,3];
/* testSimpleKeyValues */
$foo = ['1'=>1,'2'=>2,'3'=>3];
/* testMissingKeys */
$foo = ['1'=>1,2,'3'=>3];
/* testMultiTokenKeys */
$paths = array(
Init::ROOT_DIR.'/a' => 'a',
Init::ROOT_DIR.'/b' => 'b',
);
/* testMissingKeysCoalesceTernary */
return [
$a => static function () { return [1,2,3]; },
$b ?? $c,
$d ? [$e] : [$f],
];
/* testTernaryValues */
$foo = [
'1' => $row['status'] === 'rejected'
? self::REJECTED_CODE
: self::VERIFIED_CODE,
'2' => in_array($row['status'], array('notverified', 'unverified'), true)
? self::STATUS_PENDING
: self::STATUS_VERIFIED,
'3' => strtotime($row['date']),
];
/* testHeredocValues */
$foo = array(
<<<HERE
HERE
,
<<<HERE
HERE
,
);
/* testArrowFunctionValue */
$foo = array(
1 => '1',
2 => fn ($x) => yield 'a' => $x,
3 => '3',
);
@@ -0,0 +1,290 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Sniffs\AbstractArraySniff.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-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\Sniffs;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
class AbstractArraySniffTest extends AbstractMethodUnitTest
{
/**
* The sniff objects we are testing.
*
* This extends the \PHP_CodeSniffer\Sniffs\AbstractArraySniff class to make the
* internal workings of the sniff observable.
*
* @var \PHP_CodeSniffer\Sniffs\AbstractArraySniffTestable
*/
protected static $sniff;
/**
* Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file.
*
* The test case file for a unit test class has to be in the same directory
* directory and use the same file name as the test class, using the .inc extension.
*
* @return void
*/
public static function setUpBeforeClass()
{
self::$sniff = new AbstractArraySniffTestable();
parent::setUpBeforeClass();
}//end setUpBeforeClass()
/**
* Test an array of simple values only.
*
* @return void
*/
public function testSimpleValues()
{
$token = $this->getTargetToken('/* testSimpleValues */', T_OPEN_SHORT_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => ['value_start' => ($token + 1)],
1 => ['value_start' => ($token + 3)],
2 => ['value_start' => ($token + 5)],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testSimpleValues()
/**
* Test an array of simple keys and values.
*
* @return void
*/
public function testSimpleKeyValues()
{
$token = $this->getTargetToken('/* testSimpleKeyValues */', T_OPEN_SHORT_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 1),
'index_end' => ($token + 1),
'arrow' => ($token + 2),
'value_start' => ($token + 3),
],
1 => [
'index_start' => ($token + 5),
'index_end' => ($token + 5),
'arrow' => ($token + 6),
'value_start' => ($token + 7),
],
2 => [
'index_start' => ($token + 9),
'index_end' => ($token + 9),
'arrow' => ($token + 10),
'value_start' => ($token + 11),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testSimpleKeyValues()
/**
* Test an array of simple keys and values.
*
* @return void
*/
public function testMissingKeys()
{
$token = $this->getTargetToken('/* testMissingKeys */', T_OPEN_SHORT_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 1),
'index_end' => ($token + 1),
'arrow' => ($token + 2),
'value_start' => ($token + 3),
],
1 => [
'value_start' => ($token + 5),
],
2 => [
'index_start' => ($token + 7),
'index_end' => ($token + 7),
'arrow' => ($token + 8),
'value_start' => ($token + 9),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testMissingKeys()
/**
* Test an array with keys that span multiple tokens.
*
* @return void
*/
public function testMultiTokenKeys()
{
$token = $this->getTargetToken('/* testMultiTokenKeys */', T_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 4),
'index_end' => ($token + 8),
'arrow' => ($token + 10),
'value_start' => ($token + 12),
],
1 => [
'index_start' => ($token + 16),
'index_end' => ($token + 20),
'arrow' => ($token + 22),
'value_start' => ($token + 24),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testMultiTokenKeys()
/**
* Test an array of simple keys and values.
*
* @return void
*/
public function testMissingKeysCoalesceTernary()
{
$token = $this->getTargetToken('/* testMissingKeysCoalesceTernary */', T_OPEN_SHORT_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 3),
'index_end' => ($token + 3),
'arrow' => ($token + 5),
'value_start' => ($token + 7),
],
1 => [
'value_start' => ($token + 31),
],
2 => [
'value_start' => ($token + 39),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testMissingKeysCoalesceTernary()
/**
* Test an array of ternary values.
*
* @return void
*/
public function testTernaryValues()
{
$token = $this->getTargetToken('/* testTernaryValues */', T_OPEN_SHORT_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 3),
'index_end' => ($token + 3),
'arrow' => ($token + 5),
'value_start' => ($token + 7),
],
1 => [
'index_start' => ($token + 32),
'index_end' => ($token + 32),
'arrow' => ($token + 34),
'value_start' => ($token + 36),
],
2 => [
'index_start' => ($token + 72),
'index_end' => ($token + 72),
'arrow' => ($token + 74),
'value_start' => ($token + 76),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testTernaryValues()
/**
* Test an array of heredocs.
*
* @return void
*/
public function testHeredocValues()
{
$token = $this->getTargetToken('/* testHeredocValues */', T_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'value_start' => ($token + 4),
],
1 => [
'value_start' => ($token + 10),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testHeredocValues()
/**
* Test an array of with an arrow function as a value.
*
* @return void
*/
public function testArrowFunctionValue()
{
$token = $this->getTargetToken('/* testArrowFunctionValue */', T_ARRAY);
self::$sniff->process(self::$phpcsFile, $token);
$expected = [
0 => [
'index_start' => ($token + 4),
'index_end' => ($token + 4),
'arrow' => ($token + 6),
'value_start' => ($token + 8),
],
1 => [
'index_start' => ($token + 12),
'index_end' => ($token + 12),
'arrow' => ($token + 14),
'value_start' => ($token + 16),
],
2 => [
'index_start' => ($token + 34),
'index_end' => ($token + 34),
'arrow' => ($token + 36),
'value_start' => ($token + 38),
],
];
$this->assertSame($expected, self::$sniff->indicies);
}//end testArrowFunctionValue()
}//end class
@@ -0,0 +1,65 @@
<?php
/**
* A testable implementation of \PHP_CodeSniffer\Sniffs\AbstractArraySniff.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-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\Sniffs;
use PHP_CodeSniffer\Sniffs\AbstractArraySniff;
class AbstractArraySniffTestable extends AbstractArraySniff
{
/**
* The array indicies that were found during processing.
*
* @var array
*/
public $indicies = [];
/**
* Processes a single-line array definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $arrayStart The token that starts the array definition.
* @param int $arrayEnd The token that ends the array definition.
* @param array $indices An array of token positions for the array keys,
* double arrows, and values.
*
* @return void
*/
public function processSingleLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices)
{
$this->indicies = $indices;
}//end processSingleLineArray()
/**
* Processes a multi-line array definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being checked.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $arrayStart The token that starts the array definition.
* @param int $arrayEnd The token that ends the array definition.
* @param array $indices An array of token positions for the array keys,
* double arrows, and values.
*
* @return void
*/
public function processMultiLineArray($phpcsFile, $stackPtr, $arrayStart, $arrayEnd, $indices)
{
$this->indicies = $indices;
}//end processMultiLineArray()
}//end class
@@ -0,0 +1,19 @@
<?php
/* testNoParentheses */
$anonClass = new class {
function __construct() {}
};
/* testNoParenthesesAndEmptyTokens */
$anonClass = new class // phpcs:ignore Standard.Cat
{
function __construct() {}
};
/* testWithParentheses */
$anonClass = new class() {};
/* testWithParenthesesAndEmptyTokens */
$anonClass = new class /*comment */
() {};
@@ -0,0 +1,144 @@
<?php
/**
* Tests the adding of the "parenthesis" keys to an anonymous class token.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 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 AnonClassParenthesisOwnerTest extends AbstractMethodUnitTest
{
/**
* Test that anonymous class tokens without parenthesis do not get assigned a parenthesis owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassNoParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassNoParentheses($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
$this->assertFalse(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
$this->assertFalse(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
$this->assertFalse(array_key_exists('parenthesis_closer', $tokens[$anonClass]));
}//end testAnonClassNoParentheses()
/**
* Test that the next open/close parenthesis after an anonymous class without parenthesis
* do not get assigned the anonymous class as a parenthesis owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassNoParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassNoParenthesesNextOpenClose($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$function = $this->getTargetToken($testMarker, T_FUNCTION);
$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
$this->assertSame($function, $tokens[$opener]['parenthesis_owner']);
$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
$this->assertSame($function, $tokens[$closer]['parenthesis_owner']);
}//end testAnonClassNoParenthesesNextOpenClose()
/**
* Data provider.
*
* @see testAnonClassNoParentheses()
* @see testAnonClassNoParenthesesNextOpenClose()
*
* @return array
*/
public function dataAnonClassNoParentheses()
{
return [
['/* testNoParentheses */'],
['/* testNoParenthesesAndEmptyTokens */'],
];
}//end dataAnonClassNoParentheses()
/**
* Test that anonymous class tokens with parenthesis get assigned a parenthesis owner,
* opener and closer; and that the opener/closer get the anonymous class assigned as owner.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataAnonClassWithParentheses
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testAnonClassWithParentheses($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$anonClass = $this->getTargetToken($testMarker, T_ANON_CLASS);
$opener = $this->getTargetToken($testMarker, T_OPEN_PARENTHESIS);
$closer = $this->getTargetToken($testMarker, T_CLOSE_PARENTHESIS);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$anonClass]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$anonClass]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$anonClass]));
$this->assertSame($anonClass, $tokens[$anonClass]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$anonClass]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$anonClass]['parenthesis_closer']);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$opener]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$opener]));
$this->assertSame($anonClass, $tokens[$opener]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$opener]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$opener]['parenthesis_closer']);
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]));
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$closer]));
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$closer]));
$this->assertSame($anonClass, $tokens[$closer]['parenthesis_owner']);
$this->assertSame($opener, $tokens[$closer]['parenthesis_opener']);
$this->assertSame($closer, $tokens[$closer]['parenthesis_closer']);
}//end testAnonClassWithParentheses()
/**
* Data provider.
*
* @see testAnonClassWithParentheses()
*
* @return array
*/
public function dataAnonClassWithParentheses()
{
return [
['/* testWithParentheses */'],
['/* testWithParenthesesAndEmptyTokens */'],
];
}//end dataAnonClassWithParentheses()
}//end class
@@ -0,0 +1,35 @@
<?php
/* testEmptyArray */
$var = array();
/* testArrayWithSpace */
$var = array (1 => 10);
/* testArrayWithComment */
$var = Array /*comment*/ (1 => 10);
/* testNestingArray */
$var = array(
/* testNestedArray */
array(
'key' => 'value',
/* testClosureReturnType */
'closure' => function($a) use($global) : Array {},
),
);
/* testFunctionDeclarationParamType */
function foo(array $a) {}
/* testFunctionDeclarationReturnType */
function foo($a) : int|array|null {}
class Bar {
/* testClassConst */
const ARRAY = [];
/* testClassMethod */
public function array() {}
}
@@ -0,0 +1,170 @@
<?php
/**
* Tests that the array keyword is tokenized correctly.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2021 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 ArrayKeywordTest extends AbstractMethodUnitTest
{
/**
* Test that the array keyword is correctly tokenized as `T_ARRAY`.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent Optional. The token content to look for.
*
* @dataProvider dataArrayKeyword
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap
*
* @return void
*/
public function testArrayKeyword($testMarker, $testContent='array')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_ARRAY, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_ARRAY (code)');
$this->assertSame('T_ARRAY', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_ARRAY (type)');
$this->assertArrayHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is not set');
$this->assertArrayHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is not set');
}//end testArrayKeyword()
/**
* Data provider.
*
* @see testArrayKeyword()
*
* @return array
*/
public function dataArrayKeyword()
{
return [
'empty array' => ['/* testEmptyArray */'],
'array with space before parenthesis' => ['/* testArrayWithSpace */'],
'array with comment before parenthesis' => [
'/* testArrayWithComment */',
'Array',
],
'nested: outer array' => ['/* testNestingArray */'],
'nested: inner array' => ['/* testNestedArray */'],
];
}//end dataArrayKeyword()
/**
* Test that the array keyword when used in a type declaration is correctly tokenized as `T_STRING`.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent Optional. The token content to look for.
*
* @dataProvider dataArrayType
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap
*
* @return void
*/
public function testArrayType($testMarker, $testContent='array')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)');
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)');
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set');
$this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set');
$this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set');
}//end testArrayType()
/**
* Data provider.
*
* @see testArrayType()
*
* @return array
*/
public function dataArrayType()
{
return [
'closure return type' => [
'/* testClosureReturnType */',
'Array',
],
'function param type' => ['/* testFunctionDeclarationParamType */'],
'function union return type' => ['/* testFunctionDeclarationReturnType */'],
];
}//end dataArrayType()
/**
* Verify that the retokenization of `T_ARRAY` tokens to `T_STRING` is handled correctly
* for tokens with the contents 'array' which aren't in actual fact the array keyword.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotArrayKeyword
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createTokenMap
*
* @return void
*/
public function testNotArrayKeyword($testMarker, $testContent='array')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_ARRAY, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)');
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)');
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set');
$this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set');
$this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set');
}//end testNotArrayKeyword()
/**
* Data provider.
*
* @see testNotArrayKeyword()
*
* @return array
*/
public function dataNotArrayKeyword()
{
return [
'class-constant-name' => [
'/* testClassConst */',
'ARRAY',
],
'class-method-name' => ['/* testClassMethod */'],
];
}//end dataNotArrayKeyword()
}//end class
@@ -0,0 +1,90 @@
<?php
/* testAttribute */
#[Attribute]
class CustomAttribute {}
/* testAttributeWithParams */
#[Attribute(Attribute::TARGET_CLASS)]
class SecondCustomAttribute {}
/* testAttributeWithNamedParam */
#[Attribute(flags: Attribute::TARGET_ALL)]
class AttributeWithParams {
public function __construct($foo, array $bar) {}
}
/* testAttributeOnFunction */
#[CustomAttribute]
function attribute_on_function_test() {}
/* testAttributeOnFunctionWithParams */
#[AttributeWithParams('foo', bar: ['bar' => 'foobar'])]
function attribute_with_params_on_function_test() {}
/* testAttributeWithShortClosureParameter */
#[AttributeWithParams(static fn ($value) => ! $value)]
function attribute_with_short_closure_param_test() {}
/* testTwoAttributeOnTheSameLine */
#[CustomAttribute] #[AttributeWithParams('foo')]
function two_attribute_on_same_line_test() {}
/* testAttributeAndCommentOnTheSameLine */
#[CustomAttribute] // This is a comment
function attribute_and_line_comment_on_same_line_test() {}
/* testAttributeGrouping */
#[CustomAttribute, AttributeWithParams('foo'), AttributeWithParams('foo', bar: ['bar' => 'foobar'])]
function attribute_grouping_test() {}
/* testAttributeMultiline */
#[
CustomAttribute,
AttributeWithParams('foo'),
AttributeWithParams('foo', bar: ['bar' => 'foobar'])
]
function attribute_multiline_test() {}
/* testAttributeMultilineWithComment */
#[
CustomAttribute, // comment
AttributeWithParams(/* another comment */ 'foo'),
AttributeWithParams('foo', bar: ['bar' => 'foobar'])
]
function attribute_multiline_with_comment_test() {}
/* testSingleAttributeOnParameter */
function single_attribute_on_parameter_test(#[ParamAttribute] int $param) {}
/* testMultipleAttributesOnParameter */
function multiple_attributes_on_parameter_test(#[ParamAttribute, AttributeWithParams(/* another comment */ 'foo')] int $param) {}
/* testFqcnAttribute */
#[Boo\QualifiedName, \Foo\FullyQualifiedName('foo')]
function fqcn_attrebute_test() {}
/* testNestedAttributes */
#[Boo\QualifiedName(fn (#[AttributeOne('boo')] $value) => (string) $value)]
function nested_attributes_test() {}
/* testMultilineAttributesOnParameter */
function multiline_attributes_on_parameter_test(#[
AttributeWithParams(
'foo'
)
] int $param) {}
/* testAttributeContainingTextLookingLikeCloseTag */
#[DeprecationReason('reason: <https://some-website/reason?>')]
function attribute_containing_text_looking_like_close_tag() {}
/* testAttributeContainingMultilineTextLookingLikeCloseTag */
#[DeprecationReason(
'reason: <https://some-website/reason?>'
)]
function attribute_containing_mulitline_text_looking_like_close_tag() {}
/* testInvalidAttribute */
#[ThisIsNotAnAttribute
function invalid_attribute_test() {}
@@ -0,0 +1,658 @@
<?php
/**
* Tests the support of PHP 8 attributes
*
* @author Alessandro Chitolina <alekitto@gmail.com>
* @copyright 2019 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 AttributesTest extends AbstractMethodUnitTest
{
/**
* Test that attributes are parsed correctly.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int $length The number of tokens between opener and closer.
* @param array $tokenCodes The codes of tokens inside the attributes.
*
* @dataProvider dataAttribute
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testAttribute($testMarker, $length, $tokenCodes)
{
$tokens = self::$phpcsFile->getTokens();
$attribute = $this->getTargetToken($testMarker, T_ATTRIBUTE);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(($attribute + $length), $closer);
$this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']);
$this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']);
$this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']);
$map = array_map(
function ($token) use ($attribute, $length) {
$this->assertArrayHasKey('attribute_closer', $token);
$this->assertSame(($attribute + $length), $token['attribute_closer']);
return $token['code'];
},
array_slice($tokens, ($attribute + 1), ($length - 1))
);
$this->assertSame($tokenCodes, $map);
}//end testAttribute()
/**
* Data provider.
*
* @see testAttribute()
*
* @return array
*/
public function dataAttribute()
{
return [
[
'/* testAttribute */',
2,
[ T_STRING ],
],
[
'/* testAttributeWithParams */',
7,
[
T_STRING,
T_OPEN_PARENTHESIS,
T_STRING,
T_DOUBLE_COLON,
T_STRING,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testAttributeWithNamedParam */',
10,
[
T_STRING,
T_OPEN_PARENTHESIS,
T_PARAM_NAME,
T_COLON,
T_WHITESPACE,
T_STRING,
T_DOUBLE_COLON,
T_STRING,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testAttributeOnFunction */',
2,
[ T_STRING ],
],
[
'/* testAttributeOnFunctionWithParams */',
17,
[
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_COMMA,
T_WHITESPACE,
T_PARAM_NAME,
T_COLON,
T_WHITESPACE,
T_OPEN_SHORT_ARRAY,
T_CONSTANT_ENCAPSED_STRING,
T_WHITESPACE,
T_DOUBLE_ARROW,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_SHORT_ARRAY,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testAttributeWithShortClosureParameter */',
17,
[
T_STRING,
T_OPEN_PARENTHESIS,
T_STATIC,
T_WHITESPACE,
T_FN,
T_WHITESPACE,
T_OPEN_PARENTHESIS,
T_VARIABLE,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
T_FN_ARROW,
T_WHITESPACE,
T_BOOLEAN_NOT,
T_WHITESPACE,
T_VARIABLE,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testAttributeGrouping */',
26,
[
T_STRING,
T_COMMA,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
T_COMMA,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_COMMA,
T_WHITESPACE,
T_PARAM_NAME,
T_COLON,
T_WHITESPACE,
T_OPEN_SHORT_ARRAY,
T_CONSTANT_ENCAPSED_STRING,
T_WHITESPACE,
T_DOUBLE_ARROW,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_SHORT_ARRAY,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testAttributeMultiline */',
31,
[
T_WHITESPACE,
T_WHITESPACE,
T_STRING,
T_COMMA,
T_WHITESPACE,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
T_COMMA,
T_WHITESPACE,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_COMMA,
T_WHITESPACE,
T_PARAM_NAME,
T_COLON,
T_WHITESPACE,
T_OPEN_SHORT_ARRAY,
T_CONSTANT_ENCAPSED_STRING,
T_WHITESPACE,
T_DOUBLE_ARROW,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_SHORT_ARRAY,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
],
],
[
'/* testFqcnAttribute */',
13,
[
T_STRING,
T_NS_SEPARATOR,
T_STRING,
T_COMMA,
T_WHITESPACE,
T_NS_SEPARATOR,
T_STRING,
T_NS_SEPARATOR,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
],
],
];
}//end dataAttribute()
/**
* Test that multiple attributes on the same line are parsed correctly.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testTwoAttributesOnTheSameLine()
{
$tokens = self::$phpcsFile->getTokens();
$attribute = $this->getTargetToken('/* testTwoAttributeOnTheSameLine */', T_ATTRIBUTE);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']);
$this->assertSame(T_ATTRIBUTE, $tokens[($closer + 2)]['code']);
$this->assertArrayHasKey('attribute_closer', $tokens[($closer + 2)]);
}//end testTwoAttributesOnTheSameLine()
/**
* Test that attribute followed by a line comment is parsed correctly.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testAttributeAndLineComment()
{
$tokens = self::$phpcsFile->getTokens();
$attribute = $this->getTargetToken('/* testAttributeAndCommentOnTheSameLine */', T_ATTRIBUTE);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']);
$this->assertSame(T_COMMENT, $tokens[($closer + 2)]['code']);
}//end testAttributeAndLineComment()
/**
* Test that attributes on function declaration parameters are parsed correctly.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int $position The token position (starting from T_FUNCTION) of T_ATTRIBUTE token.
* @param int $length The number of tokens between opener and closer.
* @param array $tokenCodes The codes of tokens inside the attributes.
*
* @dataProvider dataAttributeOnParameters
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testAttributeOnParameters($testMarker, $position, $length, array $tokenCodes)
{
$tokens = self::$phpcsFile->getTokens();
$function = $this->getTargetToken($testMarker, T_FUNCTION);
$attribute = ($function + $position);
$this->assertSame(T_ATTRIBUTE, $tokens[$attribute]['code']);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$this->assertSame(($attribute + $length), $tokens[$attribute]['attribute_closer']);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(T_WHITESPACE, $tokens[($closer + 1)]['code']);
$this->assertSame(T_STRING, $tokens[($closer + 2)]['code']);
$this->assertSame('int', $tokens[($closer + 2)]['content']);
$this->assertSame(T_VARIABLE, $tokens[($closer + 4)]['code']);
$this->assertSame('$param', $tokens[($closer + 4)]['content']);
$map = array_map(
function ($token) use ($attribute, $length) {
$this->assertArrayHasKey('attribute_closer', $token);
$this->assertSame(($attribute + $length), $token['attribute_closer']);
return $token['code'];
},
array_slice($tokens, ($attribute + 1), ($length - 1))
);
$this->assertSame($tokenCodes, $map);
}//end testAttributeOnParameters()
/**
* Data provider.
*
* @see testAttributeOnParameters()
*
* @return array
*/
public function dataAttributeOnParameters()
{
return [
[
'/* testSingleAttributeOnParameter */',
4,
2,
[T_STRING],
],
[
'/* testMultipleAttributesOnParameter */',
4,
10,
[
T_STRING,
T_COMMA,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_COMMENT,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
],
],
[
'/* testMultilineAttributesOnParameter */',
4,
13,
[
T_WHITESPACE,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_WHITESPACE,
T_WHITESPACE,
T_CONSTANT_ENCAPSED_STRING,
T_WHITESPACE,
T_WHITESPACE,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
T_WHITESPACE,
],
],
];
}//end dataAttributeOnParameters()
/**
* Test that an attribute containing text which looks like a PHP close tag is tokenized correctly.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int $length The number of tokens between opener and closer.
* @param array $expectedTokensAttribute The codes of tokens inside the attributes.
* @param array $expectedTokensAfter The codes of tokens after the attributes.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @dataProvider dataAttributeOnTextLookingLikeCloseTag
*
* @return void
*/
public function testAttributeContainingTextLookingLikeCloseTag($testMarker, $length, array $expectedTokensAttribute, array $expectedTokensAfter)
{
$tokens = self::$phpcsFile->getTokens();
$attribute = $this->getTargetToken($testMarker, T_ATTRIBUTE);
$this->assertSame('T_ATTRIBUTE', $tokens[$attribute]['type']);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(($attribute + $length), $closer);
$this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']);
$this->assertSame('T_ATTRIBUTE_END', $tokens[$closer]['type']);
$this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']);
$this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']);
$i = ($attribute + 1);
foreach ($expectedTokensAttribute as $item) {
list($expectedType, $expectedContents) = $item;
$this->assertSame($expectedType, $tokens[$i]['type']);
$this->assertSame($expectedContents, $tokens[$i]['content']);
$this->assertArrayHasKey('attribute_opener', $tokens[$i]);
$this->assertArrayHasKey('attribute_closer', $tokens[$i]);
++$i;
}
$i = ($closer + 1);
foreach ($expectedTokensAfter as $expectedCode) {
$this->assertSame($expectedCode, $tokens[$i]['code']);
++$i;
}
}//end testAttributeContainingTextLookingLikeCloseTag()
/**
* Data provider.
*
* @see dataAttributeOnTextLookingLikeCloseTag()
*
* @return array
*/
public function dataAttributeOnTextLookingLikeCloseTag()
{
return [
[
'/* testAttributeContainingTextLookingLikeCloseTag */',
5,
[
[
'T_STRING',
'DeprecationReason',
],
[
'T_OPEN_PARENTHESIS',
'(',
],
[
'T_CONSTANT_ENCAPSED_STRING',
"'reason: <https://some-website/reason?>'",
],
[
'T_CLOSE_PARENTHESIS',
')',
],
[
'T_ATTRIBUTE_END',
']',
],
],
[
T_WHITESPACE,
T_FUNCTION,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
T_OPEN_CURLY_BRACKET,
T_CLOSE_CURLY_BRACKET,
],
],
[
'/* testAttributeContainingMultilineTextLookingLikeCloseTag */',
8,
[
[
'T_STRING',
'DeprecationReason',
],
[
'T_OPEN_PARENTHESIS',
'(',
],
[
'T_WHITESPACE',
"\n",
],
[
'T_WHITESPACE',
" ",
],
[
'T_CONSTANT_ENCAPSED_STRING',
"'reason: <https://some-website/reason?>'",
],
[
'T_WHITESPACE',
"\n",
],
[
'T_CLOSE_PARENTHESIS',
')',
],
[
'T_ATTRIBUTE_END',
']',
],
],
[
T_WHITESPACE,
T_FUNCTION,
T_WHITESPACE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
T_OPEN_CURLY_BRACKET,
T_CLOSE_CURLY_BRACKET,
],
],
];
}//end dataAttributeOnTextLookingLikeCloseTag()
/**
* Test that invalid attribute (or comment starting with #[ and without ]) are parsed correctly.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testInvalidAttribute()
{
$tokens = self::$phpcsFile->getTokens();
$attribute = $this->getTargetToken('/* testInvalidAttribute */', T_ATTRIBUTE);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$this->assertNull($tokens[$attribute]['attribute_closer']);
}//end testInvalidAttribute()
/**
* Test that nested attributes are parsed correctly.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::findCloser
* @covers PHP_CodeSniffer\Tokenizers\PHP::parsePhpAttribute
*
* @return void
*/
public function testNestedAttributes()
{
$tokens = self::$phpcsFile->getTokens();
$tokenCodes = [
T_STRING,
T_NS_SEPARATOR,
T_STRING,
T_OPEN_PARENTHESIS,
T_FN,
T_WHITESPACE,
T_OPEN_PARENTHESIS,
T_ATTRIBUTE,
T_STRING,
T_OPEN_PARENTHESIS,
T_CONSTANT_ENCAPSED_STRING,
T_CLOSE_PARENTHESIS,
T_ATTRIBUTE_END,
T_WHITESPACE,
T_VARIABLE,
T_CLOSE_PARENTHESIS,
T_WHITESPACE,
T_FN_ARROW,
T_WHITESPACE,
T_STRING_CAST,
T_WHITESPACE,
T_VARIABLE,
T_CLOSE_PARENTHESIS,
];
$attribute = $this->getTargetToken('/* testNestedAttributes */', T_ATTRIBUTE);
$this->assertArrayHasKey('attribute_closer', $tokens[$attribute]);
$closer = $tokens[$attribute]['attribute_closer'];
$this->assertSame(($attribute + 24), $closer);
$this->assertSame(T_ATTRIBUTE_END, $tokens[$closer]['code']);
$this->assertSame($tokens[$attribute]['attribute_opener'], $tokens[$closer]['attribute_opener']);
$this->assertSame($tokens[$attribute]['attribute_closer'], $tokens[$closer]['attribute_closer']);
$this->assertArrayNotHasKey('nested_attributes', $tokens[$attribute]);
$this->assertArrayHasKey('nested_attributes', $tokens[($attribute + 8)]);
$this->assertSame([$attribute => ($attribute + 24)], $tokens[($attribute + 8)]['nested_attributes']);
$test = function (array $tokens, $length, $nestedMap) use ($attribute) {
foreach ($tokens as $token) {
$this->assertArrayHasKey('attribute_closer', $token);
$this->assertSame(($attribute + $length), $token['attribute_closer']);
$this->assertSame($nestedMap, $token['nested_attributes']);
}
};
$test(array_slice($tokens, ($attribute + 1), 7), 24, [$attribute => $attribute + 24]);
$test(array_slice($tokens, ($attribute + 8), 1), 8 + 5, [$attribute => $attribute + 24]);
// Length here is 8 (nested attribute offset) + 5 (real length).
$test(
array_slice($tokens, ($attribute + 9), 4),
8 + 5,
[
$attribute => $attribute + 24,
$attribute + 8 => $attribute + 13,
]
);
$test(array_slice($tokens, ($attribute + 13), 1), 8 + 5, [$attribute => $attribute + 24]);
$test(array_slice($tokens, ($attribute + 14), 10), 24, [$attribute => $attribute + 24]);
$map = array_map(
static function ($token) {
return $token['code'];
},
array_slice($tokens, ($attribute + 1), 23)
);
$this->assertSame($tokenCodes, $map);
}//end testNestedAttributes()
}//end class
@@ -0,0 +1,95 @@
<?php
/* testPureEnum */
enum Foo
{
case SOME_CASE;
}
/* testBackedIntEnum */
enum Boo: int {
case ONE = 1;
case TWO = 1;
}
/* testBackedStringEnum */
enum Hoo : string
{
case ONE = 'one';
case TWO = 'two';
}
/* testComplexEnum */
enum ComplexEnum: int implements SomeInterface
{
use SomeTrait {
traitMethod as enumMethod;
}
const SOME_CONSTANT = true;
case ONE = 1;
public function someMethod(): bool
{
}
}
/* testEnumWithEnumAsClassName */
enum /* testEnumAsClassNameAfterEnumKeyword */ Enum {}
/* testEnumIsCaseInsensitive */
EnUm Enum {}
/* testEnumUsedAsClassName */
class Enum {
/* testEnumUsedAsClassConstantName */
const ENUM = 'enum';
/* testEnumUsedAsMethodName */
public function enum() {
// Do something.
/* testEnumUsedAsPropertyName */
$this->enum = 'foo';
}
}
/* testEnumUsedAsFunctionName */
function enum()
{
}
/* testDeclarationContainingComment */
enum /* comment */ Name
{
case SOME_CASE;
}
enum /* testEnumUsedAsEnumName */ Enum
{
}
/* testEnumUsedAsNamespaceName */
namespace Enum;
/* testEnumUsedAsPartOfNamespaceName */
namespace My\Enum\Collection;
/* testEnumUsedInObjectInitialization */
$obj = new Enum;
/* testEnumAsFunctionCall */
$var = enum($a, $b);
/* testEnumAsFunctionCallWithNamespace */
var = namespace\enum();
/* testClassConstantFetchWithEnumAsClassName */
echo Enum::CONSTANT;
/* testClassConstantFetchWithEnumAsConstantName */
echo ClassName::ENUM;
/* testParseErrorMissingName */
enum {
case SOME_CASE;
}
/* testParseErrorLiveCoding */
// This must be the last test in the file.
enum
@@ -0,0 +1,229 @@
<?php
/**
* Tests the support of PHP 8.1 "enum" keyword.
*
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @copyright 2021 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 BackfillEnumTest extends AbstractMethodUnitTest
{
/**
* Test that the "enum" keyword is tokenized as such.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $testContent The token content to look for.
* @param int $openerOffset Offset to find expected scope opener.
* @param int $closerOffset Offset to find expected scope closer.
*
* @dataProvider dataEnums
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testEnums($testMarker, $testContent, $openerOffset, $closerOffset)
{
$tokens = self::$phpcsFile->getTokens();
$enum = $this->getTargetToken($testMarker, [T_ENUM, T_STRING], $testContent);
$this->assertSame(T_ENUM, $tokens[$enum]['code']);
$this->assertSame('T_ENUM', $tokens[$enum]['type']);
$this->assertArrayHasKey('scope_condition', $tokens[$enum]);
$this->assertArrayHasKey('scope_opener', $tokens[$enum]);
$this->assertArrayHasKey('scope_closer', $tokens[$enum]);
$this->assertSame($enum, $tokens[$enum]['scope_condition']);
$scopeOpener = $tokens[$enum]['scope_opener'];
$scopeCloser = $tokens[$enum]['scope_closer'];
$expectedScopeOpener = ($enum + $openerOffset);
$expectedScopeCloser = ($enum + $closerOffset);
$this->assertSame($expectedScopeOpener, $scopeOpener);
$this->assertArrayHasKey('scope_condition', $tokens[$scopeOpener]);
$this->assertArrayHasKey('scope_opener', $tokens[$scopeOpener]);
$this->assertArrayHasKey('scope_closer', $tokens[$scopeOpener]);
$this->assertSame($enum, $tokens[$scopeOpener]['scope_condition']);
$this->assertSame($scopeOpener, $tokens[$scopeOpener]['scope_opener']);
$this->assertSame($scopeCloser, $tokens[$scopeOpener]['scope_closer']);
$this->assertSame($expectedScopeCloser, $scopeCloser);
$this->assertArrayHasKey('scope_condition', $tokens[$scopeCloser]);
$this->assertArrayHasKey('scope_opener', $tokens[$scopeCloser]);
$this->assertArrayHasKey('scope_closer', $tokens[$scopeCloser]);
$this->assertSame($enum, $tokens[$scopeCloser]['scope_condition']);
$this->assertSame($scopeOpener, $tokens[$scopeCloser]['scope_opener']);
$this->assertSame($scopeCloser, $tokens[$scopeCloser]['scope_closer']);
}//end testEnums()
/**
* Data provider.
*
* @see testEnums()
*
* @return array
*/
public function dataEnums()
{
return [
[
'/* testPureEnum */',
'enum',
4,
12,
],
[
'/* testBackedIntEnum */',
'enum',
7,
29,
],
[
'/* testBackedStringEnum */',
'enum',
8,
30,
],
[
'/* testComplexEnum */',
'enum',
11,
72,
],
[
'/* testEnumWithEnumAsClassName */',
'enum',
6,
7,
],
[
'/* testEnumIsCaseInsensitive */',
'EnUm',
4,
5,
],
[
'/* testDeclarationContainingComment */',
'enum',
6,
14,
],
];
}//end dataEnums()
/**
* Test that "enum" when not used as the keyword is still tokenized as `T_STRING`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotEnums
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNotEnums($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_ENUM, T_STRING], $testContent);
$this->assertSame(T_STRING, $tokens[$target]['code']);
$this->assertSame('T_STRING', $tokens[$target]['type']);
}//end testNotEnums()
/**
* Data provider.
*
* @see testNotEnums()
*
* @return array
*/
public function dataNotEnums()
{
return [
[
'/* testEnumAsClassNameAfterEnumKeyword */',
'Enum',
],
[
'/* testEnumUsedAsClassName */',
'Enum',
],
[
'/* testEnumUsedAsClassConstantName */',
'ENUM',
],
[
'/* testEnumUsedAsMethodName */',
'enum',
],
[
'/* testEnumUsedAsPropertyName */',
'enum',
],
[
'/* testEnumUsedAsFunctionName */',
'enum',
],
[
'/* testEnumUsedAsEnumName */',
'Enum',
],
[
'/* testEnumUsedAsNamespaceName */',
'Enum',
],
[
'/* testEnumUsedAsPartOfNamespaceName */',
'Enum',
],
[
'/* testEnumUsedInObjectInitialization */',
'Enum',
],
[
'/* testEnumAsFunctionCall */',
'enum',
],
[
'/* testEnumAsFunctionCallWithNamespace */',
'enum',
],
[
'/* testClassConstantFetchWithEnumAsClassName */',
'Enum',
],
[
'/* testClassConstantFetchWithEnumAsConstantName */',
'ENUM',
],
[
'/* testParseErrorMissingName */',
'enum',
],
[
'/* testParseErrorLiveCoding */',
'enum',
],
];
}//end dataNotEnums()
}//end class
@@ -0,0 +1,28 @@
<?php
/* testExplicitOctal */
$foo = 0o137041;
/* testExplicitOctalCapitalised */
$bar = 0O137041;
/* testExplicitOctalWithNumericSeparator */
$octal = 0o137_041;
/* testInvalid1 */
$foo = 0o_137;
/* testInvalid2 */
$foo = 0O_41;
/* testInvalid3 */
$foo = 0o91;
/* testInvalid4 */
$foo = 0O282;
/* testInvalid5 */
$foo = 0o28_2;
/* testInvalid6 */
$foo = 0o2_82;
@@ -0,0 +1,114 @@
<?php
/**
* Tests the tokenization of explicit octal notation to PHP < 8.1.
*
* @author Mark Baker <mark@demon-angel.eu>
* @copyright 2019 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 BackfillExplicitOctalNotationTest extends AbstractMethodUnitTest
{
/**
* Test that explicitly-defined octal values are tokenized as a single number and not as a number and a string.
*
* @param string $marker The comment which prefaces the target token in the test file.
* @param string $value The expected content of the token
* @param int|string $nextToken The expected next token.
* @param string $nextContent The expected content of the next token.
*
* @dataProvider dataExplicitOctalNotation
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testExplicitOctalNotation($marker, $value, $nextToken, $nextContent)
{
$tokens = self::$phpcsFile->getTokens();
$number = $this->getTargetToken($marker, [T_LNUMBER]);
$this->assertSame($value, $tokens[$number]['content'], 'Content of integer token does not match expectation');
$this->assertSame($nextToken, $tokens[($number + 1)]['code'], 'Next token is not the expected type, but '.$tokens[($number + 1)]['type']);
$this->assertSame($nextContent, $tokens[($number + 1)]['content'], 'Next token did not have the expected contents');
}//end testExplicitOctalNotation()
/**
* Data provider.
*
* @see testExplicitOctalNotation()
*
* @return array
*/
public function dataExplicitOctalNotation()
{
return [
[
'marker' => '/* testExplicitOctal */',
'value' => '0o137041',
'nextToken' => T_SEMICOLON,
'nextContent' => ';',
],
[
'marker' => '/* testExplicitOctalCapitalised */',
'value' => '0O137041',
'nextToken' => T_SEMICOLON,
'nextContent' => ';',
],
[
'marker' => '/* testExplicitOctalWithNumericSeparator */',
'value' => '0o137_041',
'nextToken' => T_SEMICOLON,
'nextContent' => ';',
],
[
'marker' => '/* testInvalid1 */',
'value' => '0',
'nextToken' => T_STRING,
'nextContent' => 'o_137',
],
[
'marker' => '/* testInvalid2 */',
'value' => '0',
'nextToken' => T_STRING,
'nextContent' => 'O_41',
],
[
'marker' => '/* testInvalid3 */',
'value' => '0',
'nextToken' => T_STRING,
'nextContent' => 'o91',
],
[
'marker' => '/* testInvalid4 */',
'value' => '0O2',
'nextToken' => T_LNUMBER,
'nextContent' => '82',
],
[
'marker' => '/* testInvalid5 */',
'value' => '0o2',
'nextToken' => T_LNUMBER,
'nextContent' => '8_2',
],
[
'marker' => '/* testInvalid6 */',
'value' => '0o2',
'nextToken' => T_STRING,
'nextContent' => '_82',
],
];
}//end dataExplicitOctalNotation()
}//end class
@@ -0,0 +1,198 @@
<?php
/* testStandard */
$fn1 = fn($x) => $x + $y;
/* testMixedCase */
$fn1 = Fn($x) => $x + $y;
/* testWhitespace */
$fn1 = fn ($x) => $x + $y;
/* testComment */
$fn1 = fn /* comment here */ ($x) => $x + $y;
/* testHeredoc */
$fn1 = fn() => <<<HTML
fn
HTML;
/* testFunctionName */
function fn() {}
/* testNestedOuter */
$fn = fn($x) => /* testNestedInner */ fn($y) => $x * $y + $z;
/* testNestedSharedCloserOuter */
$foo = foo(fn() => /* testNestedSharedCloserInner */ fn() => bar() === true);
/* testFunctionCall */
$extended = fn($c) => $callable($factory($c), $c);
/* testChainedFunctionCall */
$result = Collection::from([1, 2])
->map(fn($v) => $v * 2)
->reduce(/* testFunctionArgument */ fn($tmp, $v) => $tmp + $v, 0);
/* testClosure */
$extended = fn($c) => $callable(function() {
for ($x = 1; $x < 10; $x++) {
echo $x;
}
echo 'done';
}, $c);
/* testArrayIndex */
$found = in_array_cb($needle, $haystack, fn($array, $needle) => $array[2] === $needle);
$result = array_map(
/* testReturnType */
static fn(int $number) : int => $number + 1,
$numbers
);
/* testReference */
fn&($x) => $x;
/* testGrouped */
(fn($x) => $x) + $y;
/* testArrayValue */
$a = [
'a' => fn() => return 1,
];
/* testArrayValueNoTrailingComma */
$a = [
'a' => fn() => foo()
];
/* testYield */
$a = fn($x) => yield 'k' => $x;
/* testNullableNamespace */
$a = fn(?\DateTime $x) : ?\DateTime => $x;
/* testNamespaceOperatorInTypes */
$fn = fn(namespace\Foo $a) : ?namespace\Foo => $a;
/* testSelfReturnType */
fn(self $a) : self => $a;
/* testParentReturnType */
fn(parent $a) : parent => $a;
/* testCallableReturnType */
fn(callable $a) : callable => $a;
/* testArrayReturnType */
fn(array $a) : array => $a;
/* testStaticReturnType */
fn(array $a) : static => $a;
/* testUnionParamType */
$arrowWithUnionParam = fn(int|float $param) : SomeClass => new SomeClass($param);
/* testUnionReturnType */
$arrowWithUnionReturn = fn($param) : int|float => $param | 10;
/* testTernary */
$fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b';
/* testTernaryWithTypes */
$fn = fn(int|null $a) : array|null => $a ? null : [];
function matchInArrow($x) {
/* testWithMatchValue */
$fn = fn($x) => match(true) {
1, 2, 3, 4, 5 => 'foo',
default => 'bar',
};
}
function matchInArrowAndMore($x) {
/* testWithMatchValueAndMore */
$fn = fn($x) => match(true) {
1, 2, 3, 4, 5 => 'foo',
default => 'bar',
} . 'suffix';
}
function arrowFunctionInMatchWithTrailingComma($x) {
return match ($x) {
/* testInMatchNotLastValue */
1 => fn($y) => callMe($y),
/* testInMatchLastValueWithTrailingComma */
default => fn($y) => callThem($y),
};
}
function arrowFunctionInMatchNoTrailingComma1($x) {
return match ($x) {
1 => fn($y) => callMe($y),
/* testInMatchLastValueNoTrailingComma1 */
default => fn($y) => callThem($y)
};
}
function arrowFunctionInMatchNoTrailingComma2($x) {
return match ($x) {
/* testInMatchLastValueNoTrailingComma2 */
default => fn($y) => 5 * $y
};
}
/* testConstantDeclaration */
const FN = 'a';
/* testConstantDeclarationLower */
const fn = 'a';
class Foo {
/* testStaticMethodName */
public static function fn($param) {
/* testNestedInMethod */
$fn = fn($c) => $callable($factory($c), $c);
}
public function foo() {
/* testPropertyAssignment */
$this->fn = 'a';
}
}
$anon = new class() {
/* testAnonClassMethodName */
protected function fN($param) {
}
}
/* testNonArrowStaticMethodCall */
$a = Foo::fn($param);
/* testNonArrowConstantAccess */
$a = MyClass::FN;
/* testNonArrowConstantAccessMixed */
$a = MyClass::Fn;
/* testNonArrowObjectMethodCall */
$a = $obj->fn($param);
/* testNonArrowObjectMethodCallUpper */
$a = $obj->FN($param);
/* testNonArrowNamespacedFunctionCall */
$a = MyNS\Sub\Fn($param);
/* testNonArrowNamespaceOperatorFunctionCall */
$a = namespace\fn($param);
/* testNonArrowFunctionNameWithUnionTypes */
function fn(int|float $param) : string|null {}
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$fn = fn
@@ -0,0 +1,802 @@
<?php
/**
* Tests the backfilling of the T_FN token to PHP < 7.4.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2019 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 BackfillFnTokenTest extends AbstractMethodUnitTest
{
/**
* Test simple arrow functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testSimple()
{
foreach (['/* testStandard */', '/* testMixedCase */'] as $comment) {
$token = $this->getTargetToken($comment, T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 12);
}
}//end testSimple()
/**
* Test whitespace inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testWhitespace()
{
$token = $this->getTargetToken('/* testWhitespace */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 6, 13);
}//end testWhitespace()
/**
* Test comments inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testComment()
{
$token = $this->getTargetToken('/* testComment */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 8, 15);
}//end testComment()
/**
* Test heredocs inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testHeredoc()
{
$token = $this->getTargetToken('/* testHeredoc */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 4, 9);
}//end testHeredoc()
/**
* Test nested arrow functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedOuter()
{
$token = $this->getTargetToken('/* testNestedOuter */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 25);
}//end testNestedOuter()
/**
* Test nested arrow functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedInner()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNestedInner */', T_FN);
$this->backfillHelper($token, true);
$expectedScopeOpener = ($token + 5);
$expectedScopeCloser = ($token + 16);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame(($token - 4), $tokens[$closer]['scope_opener'], 'Closer scope opener is not the arrow token of the "outer" arrow function (shared scope closer)');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer is not the semicolon token');
}//end testNestedInner()
/**
* Test nested arrow functions with a shared closer.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedSharedCloser()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNestedSharedCloserOuter */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 4, 20);
$token = $this->getTargetToken('/* testNestedSharedCloserInner */', T_FN);
$this->backfillHelper($token, true);
$expectedScopeOpener = ($token + 4);
$expectedScopeCloser = ($token + 12);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for "inner" arrow function is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for "inner" arrow function is not the TRUE token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for "inner" arrow function is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for "inner" arrow function is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame(($token - 4), $tokens[$closer]['scope_opener'], 'Closer scope opener for "inner" arrow function is not the arrow token of the "outer" arrow function (shared scope closer)');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for "inner" arrow function is not the TRUE token');
}//end testNestedSharedCloser()
/**
* Test arrow functions that call functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFunctionCall()
{
$token = $this->getTargetToken('/* testFunctionCall */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 17);
}//end testFunctionCall()
/**
* Test arrow functions that are included in chained calls.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testChainedFunctionCall()
{
$token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 12, 'bracket');
}//end testChainedFunctionCall()
/**
* Test arrow functions that are used as function arguments.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFunctionArgument()
{
$token = $this->getTargetToken('/* testFunctionArgument */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 8, 15, 'comma');
}//end testFunctionArgument()
/**
* Test arrow functions that use closures.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testClosure()
{
$token = $this->getTargetToken('/* testClosure */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 60, 'comma');
}//end testClosure()
/**
* Test arrow functions using an array index.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testArrayIndex()
{
$token = $this->getTargetToken('/* testArrayIndex */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 8, 17, 'comma');
}//end testArrayIndex()
/**
* Test arrow functions with a return type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReturnType()
{
$token = $this->getTargetToken('/* testReturnType */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 11, 18, 'comma');
}//end testReturnType()
/**
* Test arrow functions that return a reference.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReference()
{
$token = $this->getTargetToken('/* testReference */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 6, 9);
}//end testReference()
/**
* Test arrow functions that are grouped by parenthesis.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testGrouped()
{
$token = $this->getTargetToken('/* testGrouped */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 8);
}//end testGrouped()
/**
* Test arrow functions that are used as array values.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testArrayValue()
{
$token = $this->getTargetToken('/* testArrayValue */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 4, 9, 'comma');
}//end testArrayValue()
/**
* Test arrow functions that are used as array values with no trailing comma.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testArrayValueNoTrailingComma()
{
$token = $this->getTargetToken('/* testArrayValueNoTrailingComma */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 4, 8, 'closing parenthesis');
}//end testArrayValueNoTrailingComma()
/**
* Test arrow functions that use the yield keyword.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testYield()
{
$token = $this->getTargetToken('/* testYield */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 14);
}//end testYield()
/**
* Test arrow functions that use nullable namespace types.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNullableNamespace()
{
$token = $this->getTargetToken('/* testNullableNamespace */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 15, 18);
}//end testNullableNamespace()
/**
* Test arrow functions that use the namespace operator in the return type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNamespaceOperatorInTypes()
{
$token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 16, 19);
}//end testNamespaceOperatorInTypes()
/**
* Test arrow functions that use self/parent/callable/array/static return types.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testKeywordReturnTypes()
{
$tokens = self::$phpcsFile->getTokens();
$testMarkers = [
'Self',
'Parent',
'Callable',
'Array',
'Static',
];
foreach ($testMarkers as $marker) {
$token = $this->getTargetToken('/* test'.$marker.'ReturnType */', T_FN);
$this->backfillHelper($token);
$expectedScopeOpener = ($token + 11);
$expectedScopeCloser = ($token + 14);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], "Scope opener is not the arrow token (for $marker)");
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], "Scope closer is not the semicolon token(for $marker)");
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], "Opener scope opener is not the arrow token(for $marker)");
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], "Opener scope closer is not the semicolon token(for $marker)");
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], "Closer scope opener is not the arrow token(for $marker)");
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], "Closer scope closer is not the semicolon token(for $marker)");
}
}//end testKeywordReturnTypes()
/**
* Test arrow function with a union parameter type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testUnionParamType()
{
$token = $this->getTargetToken('/* testUnionParamType */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 13, 21);
}//end testUnionParamType()
/**
* Test arrow function with a union return type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testUnionReturnType()
{
$token = $this->getTargetToken('/* testUnionReturnType */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 11, 18);
}//end testUnionReturnType()
/**
* Test arrow functions used in ternary operators.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testTernary()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testTernary */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 40);
$token = $this->getTargetToken('/* testTernaryThen */', T_FN);
$this->backfillHelper($token);
$expectedScopeOpener = ($token + 8);
$expectedScopeCloser = ($token + 12);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for THEN is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for THEN is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for THEN is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for THEN is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'Closer scope opener for THEN is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for THEN is not the semicolon token');
$token = $this->getTargetToken('/* testTernaryElse */', T_FN);
$this->backfillHelper($token, true);
$expectedScopeOpener = ($token + 8);
$expectedScopeCloser = ($token + 11);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener for ELSE is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer for ELSE is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener for ELSE is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer for ELSE is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame(($token - 24), $tokens[$closer]['scope_opener'], 'Closer scope opener for ELSE is not the arrow token of the "outer" arrow function (shared scope closer)');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer for ELSE is not the semicolon token');
}//end testTernary()
/**
* Test typed arrow functions used in ternary operators.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testTernaryWithTypes()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testTernaryWithTypes */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 15, 27);
}//end testTernaryWithTypes()
/**
* Test arrow function returning a match control structure.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testWithMatchValue()
{
$token = $this->getTargetToken('/* testWithMatchValue */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 44);
}//end testWithMatchValue()
/**
* Test arrow function returning a match control structure with something behind it.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testWithMatchValueAndMore()
{
$token = $this->getTargetToken('/* testWithMatchValueAndMore */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 48);
}//end testWithMatchValueAndMore()
/**
* Test match control structure returning arrow functions.
*
* @param string $testMarker The comment prefacing the target token.
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
* @param string $expectedCloserType The type of token expected for the scope closer.
* @param string $expectedCloserFriendlyName A friendly name for the type of token expected for the scope closer
* to be used in the error message for failing tests.
*
* @dataProvider dataInMatchValue
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testInMatchValue($testMarker, $openerOffset, $closerOffset, $expectedCloserType, $expectedCloserFriendlyName)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, $openerOffset, $closerOffset, $expectedCloserFriendlyName);
$this->assertSame($expectedCloserType, $tokens[($token + $closerOffset)]['type'], 'Mismatched scope closer type');
}//end testInMatchValue()
/**
* Data provider.
*
* @see testInMatchValue()
*
* @return array
*/
public function dataInMatchValue()
{
return [
'not_last_value' => [
'/* testInMatchNotLastValue */',
5,
11,
'T_COMMA',
'comma',
],
'last_value_with_trailing_comma' => [
'/* testInMatchLastValueWithTrailingComma */',
5,
11,
'T_COMMA',
'comma',
],
'last_value_without_trailing_comma_1' => [
'/* testInMatchLastValueNoTrailingComma1 */',
5,
10,
'T_CLOSE_PARENTHESIS',
'close parenthesis',
],
'last_value_without_trailing_comma_2' => [
'/* testInMatchLastValueNoTrailingComma2 */',
5,
11,
'T_VARIABLE',
'$y variable',
],
];
}//end dataInMatchValue()
/**
* Test arrow function nested within a method declaration.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedInMethod()
{
$token = $this->getTargetToken('/* testNestedInMethod */', T_FN);
$this->backfillHelper($token);
$this->scopePositionTestHelper($token, 5, 17);
}//end testNestedInMethod()
/**
* Verify that "fn" keywords which are not arrow functions get tokenized as T_STRING and don't
* have the extra token array indexes.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotAnArrowFunction
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNotAnArrowFunction($testMarker, $testContent='fn')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_STRING, T_FN], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING');
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set');
$this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set');
$this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set');
}//end testNotAnArrowFunction()
/**
* Data provider.
*
* @see testNotAnArrowFunction()
*
* @return array
*/
public function dataNotAnArrowFunction()
{
return [
['/* testFunctionName */'],
[
'/* testConstantDeclaration */',
'FN',
],
[
'/* testConstantDeclarationLower */',
'fn',
],
['/* testStaticMethodName */'],
['/* testPropertyAssignment */'],
[
'/* testAnonClassMethodName */',
'fN',
],
['/* testNonArrowStaticMethodCall */'],
[
'/* testNonArrowConstantAccess */',
'FN',
],
[
'/* testNonArrowConstantAccessMixed */',
'Fn',
],
['/* testNonArrowObjectMethodCall */'],
[
'/* testNonArrowObjectMethodCallUpper */',
'FN',
],
[
'/* testNonArrowNamespacedFunctionCall */',
'Fn',
],
['/* testNonArrowNamespaceOperatorFunctionCall */'],
['/* testNonArrowFunctionNameWithUnionTypes */'],
['/* testLiveCoding */'],
];
}//end dataNotAnArrowFunction()
/**
* Helper function to check that all token keys are correctly set for T_FN tokens.
*
* @param int $token The T_FN token to check.
* @param bool $skipScopeCloserCheck Whether to skip the scope closer check.
* This should be set to "true" when testing nested arrow functions,
* where the "inner" arrow function shares a scope closer with the
* "outer" arrow function, as the 'scope_condition' for the scope closer
* of the "inner" arrow function will point to the "outer" arrow function.
*
* @return void
*/
private function backfillHelper($token, $skipScopeCloserCheck=false)
{
$tokens = self::$phpcsFile->getTokens();
$this->assertTrue(array_key_exists('scope_condition', $tokens[$token]), 'Scope condition is not set');
$this->assertTrue(array_key_exists('scope_opener', $tokens[$token]), 'Scope opener is not set');
$this->assertTrue(array_key_exists('scope_closer', $tokens[$token]), 'Scope closer is not set');
$this->assertSame($tokens[$token]['scope_condition'], $token, 'Scope condition is not the T_FN token');
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$token]), 'Parenthesis owner is not set');
$this->assertTrue(array_key_exists('parenthesis_opener', $tokens[$token]), 'Parenthesis opener is not set');
$this->assertTrue(array_key_exists('parenthesis_closer', $tokens[$token]), 'Parenthesis closer is not set');
$this->assertSame($tokens[$token]['parenthesis_owner'], $token, 'Parenthesis owner is not the T_FN token');
$opener = $tokens[$token]['scope_opener'];
$this->assertTrue(array_key_exists('scope_condition', $tokens[$opener]), 'Opener scope condition is not set');
$this->assertTrue(array_key_exists('scope_opener', $tokens[$opener]), 'Opener scope opener is not set');
$this->assertTrue(array_key_exists('scope_closer', $tokens[$opener]), 'Opener scope closer is not set');
$this->assertSame($tokens[$opener]['scope_condition'], $token, 'Opener scope condition is not the T_FN token');
$this->assertSame(T_FN_ARROW, $tokens[$opener]['code'], 'Arrow scope opener not tokenized as T_FN_ARROW (code)');
$this->assertSame('T_FN_ARROW', $tokens[$opener]['type'], 'Arrow scope opener not tokenized as T_FN_ARROW (type)');
$closer = $tokens[$token]['scope_closer'];
$this->assertTrue(array_key_exists('scope_condition', $tokens[$closer]), 'Closer scope condition is not set');
$this->assertTrue(array_key_exists('scope_opener', $tokens[$closer]), 'Closer scope opener is not set');
$this->assertTrue(array_key_exists('scope_closer', $tokens[$closer]), 'Closer scope closer is not set');
if ($skipScopeCloserCheck === false) {
$this->assertSame($tokens[$closer]['scope_condition'], $token, 'Closer scope condition is not the T_FN token');
}
$opener = $tokens[$token]['parenthesis_opener'];
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$opener]), 'Opening parenthesis owner is not set');
$this->assertSame($tokens[$opener]['parenthesis_owner'], $token, 'Opening parenthesis owner is not the T_FN token');
$closer = $tokens[$token]['parenthesis_closer'];
$this->assertTrue(array_key_exists('parenthesis_owner', $tokens[$closer]), 'Closing parenthesis owner is not set');
$this->assertSame($tokens[$closer]['parenthesis_owner'], $token, 'Closing parenthesis owner is not the T_FN token');
}//end backfillHelper()
/**
* Helper function to check that the scope opener/closer positions are correctly set for T_FN tokens.
*
* @param int $token The T_FN token to check.
* @param int $openerOffset The expected offset of the scope opener in relation to
* the fn keyword.
* @param int $closerOffset The expected offset of the scope closer in relation to
* the fn keyword.
* @param string $expectedCloserType Optional. The type of token expected for the scope closer.
*
* @return void
*/
private function scopePositionTestHelper($token, $openerOffset, $closerOffset, $expectedCloserType='semicolon')
{
$tokens = self::$phpcsFile->getTokens();
$expectedScopeOpener = ($token + $openerOffset);
$expectedScopeCloser = ($token + $closerOffset);
$this->assertSame($expectedScopeOpener, $tokens[$token]['scope_opener'], 'Scope opener is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$token]['scope_closer'], 'Scope closer is not the '.$expectedCloserType.' token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'Opener scope opener is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'Opener scope closer is not the '.$expectedCloserType.' token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'Closer scope opener is not the arrow token');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'Closer scope closer is not the '.$expectedCloserType.' token');
}//end scopePositionTestHelper()
}//end class
@@ -0,0 +1,319 @@
<?php
function simpleMatch($x) {
/* testMatchSimple */
return match ($x) {
0 => 'Zero',
1 => 'One',
2 => 'Two',
};
}
function matchNoTrailingComma($bool) {
/* testMatchNoTrailingComma */
echo match ($bool) {
true => "true\n",
false => "false\n"
};
}
function matchWithDefault($i) {
/* testMatchWithDefault */
return match ($i) {
1 => 1,
2 => 2,
default => 'default',
};
}
function matchExpressionInCondition($i) {
/* testMatchExpressionInCondition */
return match (true) {
$i >= 50 => '50+',
$i >= 40 => '40-50',
$i >= 30 => '30-40',
$i >= 20 => '20-30',
$i >= 10 => '10-20',
default => '0-10',
};
}
function matchMultiCase($day) {
/* testMatchMultiCase */
return match ($day) {
1, 7 => false,
2, 3, 4, 5, 6 => true,
};
}
function matchMultiCaseTrailingCommaInCase($bool) {
/* testMatchMultiCaseTrailingCommaInCase */
echo match ($bool) {
false,
0,
=> "false\n",
true,
1,
=> "true\n",
default,
=> "not bool\n",
};
}
assert((function () {
/* testMatchInClosureNotLowercase */
Match ('foo') {
'foo', 'bar' => false,
'baz' => 'a',
default => 'b',
};
})());
function matchInArrowFunction($x) {
/* testMatchInArrowFunction */
$fn = fn($x) => match(true) {
1, 2, 3, 4, 5 => 'foo',
default => 'bar',
};
}
function arrowFunctionInMatchNoTrailingComma($x) {
/* testArrowFunctionInMatchNoTrailingComma */
return match ($x) {
1 => fn($y) => callMe($y),
default => fn($y) => callThem($y)
};
}
/* testMatchInFunctionCallParamNotLowercase */
var_dump(MATCH ( 'foo' ) {
'foo' => dump_and_return('foo'),
'bar' => dump_and_return('bar'),
});
/* testMatchInMethodCallParam */
Test::usesValue(match(true) { true => $i });
/* testMatchDiscardResult */
match (1) {
1 => print "Executed\n",
};
/* testMatchWithDuplicateConditionsWithComments */
echo match /*comment*/ ( $value /*comment*/ ) {
// Comment.
2, 1 => '2, 1',
1 => 1,
3 => 3,
4 => 4,
5 => 5,
};
/* testNestedMatchOuter */
$x = match ($y) {
/* testNestedMatchInner */
default => match ($z) { 1 => 1 },
};
/* testMatchInTernaryCondition */
$x = match ($test) { 1 => 'a', 2 => 'b' } ?
/* testMatchInTernaryThen */ match ($test) { 1 => 'a', 2 => 'b' } :
/* testMatchInTernaryElse */ match ($notTest) { 3 => 'a', 4 => 'b' };
/* testMatchInArrayValue */
$array = array(
match ($test) { 1 => 'a', 2 => 'b' },
);
/* testMatchInArrayKey */
$array = [
match ($test) { 1 => 'a', 2 => 'b' } => 'dynamic keys, woho!',
];
/* testMatchreturningArray */
$matcher = match ($x) {
0 => array( 0 => 1, 'a' => 2, 'b' => 3 ),
1 => [1, 2, 3],
2 => array( 1, [1, 2, 3], 2, 3),
3 => [ 0 => 1, 'a' => array(1, 2, 3), 'b' => 2, 3],
};
/* testSwitchContainingMatch */
switch ($something) {
/* testMatchWithDefaultNestedInSwitchCase1 */
case 'foo':
$var = [1, 2, 3];
$var = match ($i) {
1 => 1,
default => 'default',
};
continue 2;
/* testMatchWithDefaultNestedInSwitchCase2 */
case 'bar' ;
$i = callMe($a, $b);
return match ($i) {
1 => 1,
default => 'default',
};
/* testMatchWithDefaultNestedInSwitchDefault */
default:
echo 'something', match ($i) {
1 => 1,
default => 'default',
};
break;
}
/* testMatchContainingSwitch */
$x = match ($y) {
5, 8 => function($z) {
/* testSwitchNestedInMatch1 */
switch($z) {
case 'a':
$var = [1, 2, 3];
return 'a';
/* testSwitchDefaultNestedInMatchCase */
default:
$i = callMe($a, $b);
return 'default1';
}
},
default => function($z) {
/* testSwitchNestedInMatch2 */
switch($z) {
case 'a';
$i = callMe($a, $b);
return 'b';
/* testSwitchDefaultNestedInMatchDefault */
default;
$var = [1, 2, 3];
return 'default2';
}
}
};
/* testMatchNoCases */
// Intentional fatal error.
$x = match (true) {};
/* testMatchMultiDefault */
// Intentional fatal error.
echo match (1) {
default => 'foo',
1 => 'bar',
2 => 'baz',
default => 'qux',
};
/* testNoMatchStaticMethodCall */
$a = Foo::match($param);
/* testNoMatchClassConstantAccess */
$a = MyClass::MATCH;
/* testNoMatchClassConstantArrayAccessMixedCase */
$a = MyClass::Match[$a];
/* testNoMatchMethodCall */
$a = $obj->match($param);
/* testNoMatchMethodCallUpper */
$a = $obj??->MATCH()->chain($param);
/* testNoMatchPropertyAccess */
$a = $obj->match;
/* testNoMatchNamespacedFunctionCall */
// Intentional fatal error.
$a = MyNS\Sub\match($param);
/* testNoMatchNamespaceOperatorFunctionCall */
// Intentional fatal error.
$a = namespace\match($param);
interface MatchInterface {
/* testNoMatchInterfaceMethodDeclaration */
public static function match($param);
}
class MatchClass {
/* testNoMatchClassConstantDeclarationLower */
const match = 'a';
/* testNoMatchClassMethodDeclaration */
public static function match($param) {
/* testNoMatchPropertyAssignment */
$this->match = 'a';
}
}
/* testNoMatchClassInstantiation */
$obj = new Match();
$anon = new class() {
/* testNoMatchAnonClassMethodDeclaration */
protected function maTCH($param) {
}
};
/* testNoMatchClassDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
class Match {}
/* testNoMatchInterfaceDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
interface Match {}
/* testNoMatchTraitDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
trait Match {}
/* testNoMatchConstantDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
const MATCH = '1';
/* testNoMatchFunctionDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
function match() {}
/* testNoMatchNamespaceDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
namespace Match {}
/* testNoMatchExtendedClassDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
class Foo extends Match {}
/* testNoMatchImplementedClassDeclaration */
// Intentional fatal error. Match is now a reserved keyword.
class Bar implements Match {}
/* testNoMatchInUseStatement */
// Intentional fatal error in PHP < 8. Match is now a reserved keyword.
use Match\me;
function brokenMatchNoCurlies($x) {
/* testNoMatchMissingCurlies */
// Intentional fatal error. New control structure is not supported without curly braces.
return match ($x)
0 => 'Zero',
1 => 'One',
2 => 'Two',
;
}
function brokenMatchAlternativeSyntax($x) {
/* testNoMatchAlternativeSyntax */
// Intentional fatal error. Alternative syntax is not supported.
return match ($x) :
0 => 'Zero',
1 => 'One',
2 => 'Two',
endmatch;
}
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
echo match
@@ -0,0 +1,529 @@
<?php
/**
* Tests the backfilling of the T_MATCH token to PHP < 8.0, as well as the
* setting of parenthesis/scopes for match control structures across PHP versions.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2020-2021 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;
use PHP_CodeSniffer\Util\Tokens;
class BackfillMatchTokenTest extends AbstractMethodUnitTest
{
/**
* Test tokenization of match expressions.
*
* @param string $testMarker The comment prefacing the target token.
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
* @param string $testContent The token content to look for.
*
* @dataProvider dataMatchExpression
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testMatchExpression($testMarker, $openerOffset, $closerOffset, $testContent='match')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_MATCH, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (code)');
$this->assertSame('T_MATCH', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH (type)');
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
$this->parenthesisTestHelper($token);
}//end testMatchExpression()
/**
* Data provider.
*
* @see testMatchExpression()
*
* @return array
*/
public function dataMatchExpression()
{
return [
'simple_match' => [
'/* testMatchSimple */',
6,
33,
],
'no_trailing_comma' => [
'/* testMatchNoTrailingComma */',
6,
24,
],
'with_default_case' => [
'/* testMatchWithDefault */',
6,
33,
],
'expression_in_condition' => [
'/* testMatchExpressionInCondition */',
6,
77,
],
'multicase' => [
'/* testMatchMultiCase */',
6,
40,
],
'multicase_trailing_comma_in_case' => [
'/* testMatchMultiCaseTrailingCommaInCase */',
6,
47,
],
'in_closure_not_lowercase' => [
'/* testMatchInClosureNotLowercase */',
6,
36,
'Match',
],
'in_arrow_function' => [
'/* testMatchInArrowFunction */',
5,
36,
],
'arrow_function_in_match_no_trailing_comma' => [
'/* testArrowFunctionInMatchNoTrailingComma */',
6,
44,
],
'in_function_call_param_not_lowercase' => [
'/* testMatchInFunctionCallParamNotLowercase */',
8,
32,
'MATCH',
],
'in_method_call_param' => [
'/* testMatchInMethodCallParam */',
5,
13,
],
'discard_result' => [
'/* testMatchDiscardResult */',
6,
18,
],
'duplicate_conditions_and_comments' => [
'/* testMatchWithDuplicateConditionsWithComments */',
12,
59,
],
'nested_match_outer' => [
'/* testNestedMatchOuter */',
6,
33,
],
'nested_match_inner' => [
'/* testNestedMatchInner */',
6,
14,
],
'ternary_condition' => [
'/* testMatchInTernaryCondition */',
6,
21,
],
'ternary_then' => [
'/* testMatchInTernaryThen */',
6,
21,
],
'ternary_else' => [
'/* testMatchInTernaryElse */',
6,
21,
],
'array_value' => [
'/* testMatchInArrayValue */',
6,
21,
],
'array_key' => [
'/* testMatchInArrayKey */',
6,
21,
],
'returning_array' => [
'/* testMatchreturningArray */',
6,
125,
],
'nested_in_switch_case_1' => [
'/* testMatchWithDefaultNestedInSwitchCase1 */',
6,
25,
],
'nested_in_switch_case_2' => [
'/* testMatchWithDefaultNestedInSwitchCase2 */',
6,
25,
],
'nested_in_switch_default' => [
'/* testMatchWithDefaultNestedInSwitchDefault */',
6,
25,
],
'match_with_nested_switch' => [
'/* testMatchContainingSwitch */',
6,
180,
],
'no_cases' => [
'/* testMatchNoCases */',
6,
7,
],
'multi_default' => [
'/* testMatchMultiDefault */',
6,
40,
],
];
}//end dataMatchExpression()
/**
* Verify that "match" keywords which are not match control structures get tokenized as T_STRING
* and don't have the extra token array indexes.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotAMatchStructure
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNotAMatchStructure($testMarker, $testContent='match')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_STRING, T_MATCH], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)');
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)');
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set');
$this->assertArrayNotHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is set');
$this->assertArrayNotHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is set');
$next = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($token + 1), null, true);
if ($next !== false && $tokens[$next]['code'] === T_OPEN_PARENTHESIS) {
$this->assertArrayNotHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is set for opener after');
}
}//end testNotAMatchStructure()
/**
* Data provider.
*
* @see testNotAMatchStructure()
*
* @return array
*/
public function dataNotAMatchStructure()
{
return [
'static_method_call' => ['/* testNoMatchStaticMethodCall */'],
'class_constant_access' => [
'/* testNoMatchClassConstantAccess */',
'MATCH',
],
'class_constant_array_access' => [
'/* testNoMatchClassConstantArrayAccessMixedCase */',
'Match',
],
'method_call' => ['/* testNoMatchMethodCall */'],
'method_call_uppercase' => [
'/* testNoMatchMethodCallUpper */',
'MATCH',
],
'property_access' => ['/* testNoMatchPropertyAccess */'],
'namespaced_function_call' => ['/* testNoMatchNamespacedFunctionCall */'],
'namespace_operator_function_call' => ['/* testNoMatchNamespaceOperatorFunctionCall */'],
'interface_method_declaration' => ['/* testNoMatchInterfaceMethodDeclaration */'],
'class_constant_declaration' => ['/* testNoMatchClassConstantDeclarationLower */'],
'class_method_declaration' => ['/* testNoMatchClassMethodDeclaration */'],
'property_assigment' => ['/* testNoMatchPropertyAssignment */'],
'class_instantiation' => [
'/* testNoMatchClassInstantiation */',
'Match',
],
'anon_class_method_declaration' => [
'/* testNoMatchAnonClassMethodDeclaration */',
'maTCH',
],
'class_declaration' => [
'/* testNoMatchClassDeclaration */',
'Match',
],
'interface_declaration' => [
'/* testNoMatchInterfaceDeclaration */',
'Match',
],
'trait_declaration' => [
'/* testNoMatchTraitDeclaration */',
'Match',
],
'constant_declaration' => [
'/* testNoMatchConstantDeclaration */',
'MATCH',
],
'function_declaration' => ['/* testNoMatchFunctionDeclaration */'],
'namespace_declaration' => [
'/* testNoMatchNamespaceDeclaration */',
'Match',
],
'class_extends_declaration' => [
'/* testNoMatchExtendedClassDeclaration */',
'Match',
],
'class_implements_declaration' => [
'/* testNoMatchImplementedClassDeclaration */',
'Match',
],
'use_statement' => [
'/* testNoMatchInUseStatement */',
'Match',
],
'unsupported_inline_control_structure' => ['/* testNoMatchMissingCurlies */'],
'unsupported_alternative_syntax' => ['/* testNoMatchAlternativeSyntax */'],
'live_coding' => ['/* testLiveCoding */'],
];
}//end dataNotAMatchStructure()
/**
* Verify that the tokenization of switch structures is not affected by the backfill.
*
* @param string $testMarker The comment prefacing the target token.
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
*
* @dataProvider dataSwitchExpression
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testSwitchExpression($testMarker, $openerOffset, $closerOffset)
{
$token = $this->getTargetToken($testMarker, T_SWITCH);
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
$this->parenthesisTestHelper($token);
}//end testSwitchExpression()
/**
* Data provider.
*
* @see testSwitchExpression()
*
* @return array
*/
public function dataSwitchExpression()
{
return [
'switch_containing_match' => [
'/* testSwitchContainingMatch */',
6,
174,
],
'match_containing_switch_1' => [
'/* testSwitchNestedInMatch1 */',
5,
63,
],
'match_containing_switch_2' => [
'/* testSwitchNestedInMatch2 */',
5,
63,
],
];
}//end dataSwitchExpression()
/**
* Verify that the tokenization of a switch case/default structure containing a match structure
* or contained *in* a match structure is not affected by the backfill.
*
* @param string $testMarker The comment prefacing the target token.
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
*
* @dataProvider dataSwitchCaseVersusMatch
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testSwitchCaseVersusMatch($testMarker, $openerOffset, $closerOffset)
{
$token = $this->getTargetToken($testMarker, [T_CASE, T_DEFAULT]);
$this->scopeTestHelper($token, $openerOffset, $closerOffset);
}//end testSwitchCaseVersusMatch()
/**
* Data provider.
*
* @see testSwitchCaseVersusMatch()
*
* @return array
*/
public function dataSwitchCaseVersusMatch()
{
return [
'switch_with_nested_match_case_1' => [
'/* testMatchWithDefaultNestedInSwitchCase1 */',
3,
55,
],
'switch_with_nested_match_case_2' => [
'/* testMatchWithDefaultNestedInSwitchCase2 */',
4,
21,
],
'switch_with_nested_match_default_case' => [
'/* testMatchWithDefaultNestedInSwitchDefault */',
1,
38,
],
'match_with_nested_switch_case' => [
'/* testSwitchDefaultNestedInMatchCase */',
1,
18,
],
'match_with_nested_switch_default_case' => [
'/* testSwitchDefaultNestedInMatchDefault */',
1,
20,
],
];
}//end dataSwitchCaseVersusMatch()
/**
* Helper function to verify that all scope related array indexes for a control structure
* are set correctly.
*
* @param string $token The control structure token to check.
* @param int $openerOffset The expected offset of the scope opener in relation to
* the control structure token.
* @param int $closerOffset The expected offset of the scope closer in relation to
* the control structure token.
* @param bool $skipScopeCloserCheck Whether to skip the scope closer check.
* This should be set to "true" when testing nested arrow functions,
* where the "inner" arrow function shares a scope closer with the
* "outer" arrow function, as the 'scope_condition' for the scope closer
* of the "inner" arrow function will point to the "outer" arrow function.
*
* @return void
*/
private function scopeTestHelper($token, $openerOffset, $closerOffset, $skipScopeCloserCheck=false)
{
$tokens = self::$phpcsFile->getTokens();
$tokenArray = $tokens[$token];
$tokenType = $tokenArray['type'];
$expectedScopeOpener = ($token + $openerOffset);
$expectedScopeCloser = ($token + $closerOffset);
$this->assertArrayHasKey('scope_condition', $tokenArray, 'Scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokenArray, 'Scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokenArray, 'Scope closer is not set');
$this->assertSame($token, $tokenArray['scope_condition'], 'Scope condition is not the '.$tokenType.' token');
$this->assertSame($expectedScopeOpener, $tokenArray['scope_opener'], 'Scope opener of the '.$tokenType.' token incorrect');
$this->assertSame($expectedScopeCloser, $tokenArray['scope_closer'], 'Scope closer of the '.$tokenType.' token incorrect');
$opener = $tokenArray['scope_opener'];
$this->assertArrayHasKey('scope_condition', $tokens[$opener], 'Opener scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Opener scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Opener scope closer is not set');
$this->assertSame($token, $tokens[$opener]['scope_condition'], 'Opener scope condition is not the '.$tokenType.' token');
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], $tokenType.' opener scope opener token incorrect');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], $tokenType.' opener scope closer token incorrect');
$closer = $tokenArray['scope_closer'];
$this->assertArrayHasKey('scope_condition', $tokens[$closer], 'Closer scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Closer scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Closer scope closer is not set');
if ($skipScopeCloserCheck === false) {
$this->assertSame($token, $tokens[$closer]['scope_condition'], 'Closer scope condition is not the '.$tokenType.' token');
}
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], $tokenType.' closer scope opener token incorrect');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], $tokenType.' closer scope closer token incorrect');
if (($opener + 1) !== $closer) {
for ($i = ($opener + 1); $i < $closer; $i++) {
$this->assertArrayHasKey(
$token,
$tokens[$i]['conditions'],
$tokenType.' condition not added for token belonging to the '.$tokenType.' structure'
);
}
}
}//end scopeTestHelper()
/**
* Helper function to verify that all parenthesis related array indexes for a control structure
* token are set correctly.
*
* @param int $token The position of the control structure token.
*
* @return void
*/
private function parenthesisTestHelper($token)
{
$tokens = self::$phpcsFile->getTokens();
$tokenArray = $tokens[$token];
$tokenType = $tokenArray['type'];
$this->assertArrayHasKey('parenthesis_owner', $tokenArray, 'Parenthesis owner is not set');
$this->assertArrayHasKey('parenthesis_opener', $tokenArray, 'Parenthesis opener is not set');
$this->assertArrayHasKey('parenthesis_closer', $tokenArray, 'Parenthesis closer is not set');
$this->assertSame($token, $tokenArray['parenthesis_owner'], 'Parenthesis owner is not the '.$tokenType.' token');
$opener = $tokenArray['parenthesis_opener'];
$this->assertArrayHasKey('parenthesis_owner', $tokens[$opener], 'Opening parenthesis owner is not set');
$this->assertSame($token, $tokens[$opener]['parenthesis_owner'], 'Opening parenthesis owner is not the '.$tokenType.' token');
$closer = $tokenArray['parenthesis_closer'];
$this->assertArrayHasKey('parenthesis_owner', $tokens[$closer], 'Closing parenthesis owner is not set');
$this->assertSame($token, $tokens[$closer]['parenthesis_owner'], 'Closing parenthesis owner is not the '.$tokenType.' token');
}//end parenthesisTestHelper()
}//end class
@@ -0,0 +1,94 @@
<?php
/*
* Numbers with PHP 7.4 underscore separators.
*/
/* testSimpleLNumber */
$foo = 1_000_000_000;
/* testSimpleDNumber */
$foo = 107_925_284.88;
/* testFloat */
$foo = 6.674_083e-11;
/* testFloat2 */
$foo = 6.674_083e+11;
/* testFloat3 */
$foo = 1_2.3_4e1_23;
/* testHex */
$foo = 0xCAFE_F00D;
/* testHexMultiple */
$foo = 0x42_72_6F_77_6E;
/* testHexInt */
$foo = 0x42_72_6F;
/* testBinary */
$foo = 0b0101_1111;
/* testOctal */
$foo = 0137_041;
/* testExplicitOctal */
$foo = 0o137_041;
/* testExplicitOctalCapitalised */
$foo = 0O137_041;
/* testIntMoreThanMax */
$foo = 10_223_372_036_854_775_807;
/*
* Invalid use of numeric separators. These should not be touched by the backfill.
*/
/* testInvalid1 */
$a = 100_; // trailing
/* testInvalid2 */
$a = 1__1; // next to underscore
/* testInvalid3 */
$a = 1_.0; // next to decimal point
/* testInvalid4 */
$a = 1._0; // next to decimal point
/* testInvalid5 */
$a = 0x_123; // next to x
/* testInvalid6 */
$a = 0b_101; // next to b
/* testInvalid7 */
$a = 1_e2; // next to e
/* testInvalid8 */
$a = 1e_2; // next to e
/* testInvalid9 */
$testValue = 107_925_284 .88;
/* testInvalid10 */
$testValue = 107_925_284/*comment*/.88;
/* testInvalid11 */
$foo = 0o_137;
/* testInvalid12 */
$foo = 0O_41;
/*
* Ensure that legitimate calculations are not touched by the backfill.
*/
/* testCalc1 */
$a = 667_083 - 11; // Calculation.
/* test Calc2 */
$a = 6.674_08e3 + 11; // Calculation.
@@ -0,0 +1,420 @@
<?php
/**
* Tests the backfilling of numeric separators to PHP < 7.4.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2019 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 BackfillNumericSeparatorTest extends AbstractMethodUnitTest
{
/**
* Test that numbers using numeric separators are tokenized correctly.
*
* @param array $testData The data required for the specific test case.
*
* @dataProvider dataTestBackfill
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testBackfill($testData)
{
$tokens = self::$phpcsFile->getTokens();
$number = $this->getTargetToken($testData['marker'], [T_LNUMBER, T_DNUMBER]);
$this->assertSame(constant($testData['type']), $tokens[$number]['code']);
$this->assertSame($testData['type'], $tokens[$number]['type']);
$this->assertSame($testData['value'], $tokens[$number]['content']);
}//end testBackfill()
/**
* Data provider.
*
* @see testBackfill()
*
* @return array
*/
public function dataTestBackfill()
{
$testHexType = 'T_LNUMBER';
if (PHP_INT_MAX < 0xCAFEF00D) {
$testHexType = 'T_DNUMBER';
}
$testHexMultipleType = 'T_LNUMBER';
if (PHP_INT_MAX < 0x42726F776E) {
$testHexMultipleType = 'T_DNUMBER';
}
$testIntMoreThanMaxType = 'T_LNUMBER';
if (PHP_INT_MAX < 10223372036854775807) {
$testIntMoreThanMaxType = 'T_DNUMBER';
}
return [
[
[
'marker' => '/* testSimpleLNumber */',
'type' => 'T_LNUMBER',
'value' => '1_000_000_000',
],
],
[
[
'marker' => '/* testSimpleDNumber */',
'type' => 'T_DNUMBER',
'value' => '107_925_284.88',
],
],
[
[
'marker' => '/* testFloat */',
'type' => 'T_DNUMBER',
'value' => '6.674_083e-11',
],
],
[
[
'marker' => '/* testFloat2 */',
'type' => 'T_DNUMBER',
'value' => '6.674_083e+11',
],
],
[
[
'marker' => '/* testFloat3 */',
'type' => 'T_DNUMBER',
'value' => '1_2.3_4e1_23',
],
],
[
[
'marker' => '/* testHex */',
'type' => $testHexType,
'value' => '0xCAFE_F00D',
],
],
[
[
'marker' => '/* testHexMultiple */',
'type' => $testHexMultipleType,
'value' => '0x42_72_6F_77_6E',
],
],
[
[
'marker' => '/* testHexInt */',
'type' => 'T_LNUMBER',
'value' => '0x42_72_6F',
],
],
[
[
'marker' => '/* testBinary */',
'type' => 'T_LNUMBER',
'value' => '0b0101_1111',
],
],
[
[
'marker' => '/* testOctal */',
'type' => 'T_LNUMBER',
'value' => '0137_041',
],
],
[
[
'marker' => '/* testExplicitOctal */',
'type' => 'T_LNUMBER',
'value' => '0o137_041',
],
],
[
[
'marker' => '/* testExplicitOctalCapitalised */',
'type' => 'T_LNUMBER',
'value' => '0O137_041',
],
],
[
[
'marker' => '/* testIntMoreThanMax */',
'type' => $testIntMoreThanMaxType,
'value' => '10_223_372_036_854_775_807',
],
],
];
}//end dataTestBackfill()
/**
* Test that numbers using numeric separators which are considered parse errors and/or
* which aren't relevant to the backfill, do not incorrectly trigger the backfill anyway.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array $expectedTokens The token type and content of the expected token sequence.
*
* @dataProvider dataNoBackfill
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNoBackfill($testMarker, $expectedTokens)
{
$tokens = self::$phpcsFile->getTokens();
$number = $this->getTargetToken($testMarker, [T_LNUMBER, T_DNUMBER]);
foreach ($expectedTokens as $key => $expectedToken) {
$i = ($number + $key);
$this->assertSame($expectedToken['code'], $tokens[$i]['code']);
$this->assertSame($expectedToken['content'], $tokens[$i]['content']);
}
}//end testNoBackfill()
/**
* Data provider.
*
* @see testBackfill()
*
* @return array
*/
public function dataNoBackfill()
{
return [
[
'/* testInvalid1 */',
[
[
'code' => T_LNUMBER,
'content' => '100',
],
[
'code' => T_STRING,
'content' => '_',
],
],
],
[
'/* testInvalid2 */',
[
[
'code' => T_LNUMBER,
'content' => '1',
],
[
'code' => T_STRING,
'content' => '__1',
],
],
],
[
'/* testInvalid3 */',
[
[
'code' => T_LNUMBER,
'content' => '1',
],
[
'code' => T_STRING,
'content' => '_',
],
[
'code' => T_DNUMBER,
'content' => '.0',
],
],
],
[
'/* testInvalid4 */',
[
[
'code' => T_DNUMBER,
'content' => '1.',
],
[
'code' => T_STRING,
'content' => '_0',
],
],
],
[
'/* testInvalid5 */',
[
[
'code' => T_LNUMBER,
'content' => '0',
],
[
'code' => T_STRING,
'content' => 'x_123',
],
],
],
[
'/* testInvalid6 */',
[
[
'code' => T_LNUMBER,
'content' => '0',
],
[
'code' => T_STRING,
'content' => 'b_101',
],
],
],
[
'/* testInvalid7 */',
[
[
'code' => T_LNUMBER,
'content' => '1',
],
[
'code' => T_STRING,
'content' => '_e2',
],
],
],
[
'/* testInvalid8 */',
[
[
'code' => T_LNUMBER,
'content' => '1',
],
[
'code' => T_STRING,
'content' => 'e_2',
],
],
],
[
'/* testInvalid9 */',
[
[
'code' => T_LNUMBER,
'content' => '107_925_284',
],
[
'code' => T_WHITESPACE,
'content' => ' ',
],
[
'code' => T_DNUMBER,
'content' => '.88',
],
],
],
[
'/* testInvalid10 */',
[
[
'code' => T_LNUMBER,
'content' => '107_925_284',
],
[
'code' => T_COMMENT,
'content' => '/*comment*/',
],
[
'code' => T_DNUMBER,
'content' => '.88',
],
],
],
[
'/* testInvalid11 */',
[
[
'code' => T_LNUMBER,
'content' => '0',
],
[
'code' => T_STRING,
'content' => 'o_137',
],
],
],
[
'/* testInvalid12 */',
[
[
'code' => T_LNUMBER,
'content' => '0',
],
[
'code' => T_STRING,
'content' => 'O_41',
],
],
],
[
'/* testCalc1 */',
[
[
'code' => T_LNUMBER,
'content' => '667_083',
],
[
'code' => T_WHITESPACE,
'content' => ' ',
],
[
'code' => T_MINUS,
'content' => '-',
],
[
'code' => T_WHITESPACE,
'content' => ' ',
],
[
'code' => T_LNUMBER,
'content' => '11',
],
],
],
[
'/* test Calc2 */',
[
[
'code' => T_DNUMBER,
'content' => '6.674_08e3',
],
[
'code' => T_WHITESPACE,
'content' => ' ',
],
[
'code' => T_PLUS,
'content' => '+',
],
[
'code' => T_WHITESPACE,
'content' => ' ',
],
[
'code' => T_LNUMBER,
'content' => '11',
],
],
],
];
}//end dataNoBackfill()
}//end class
@@ -0,0 +1,100 @@
<?php
class Foo
{
/* testReadonlyProperty */
readonly int $readonlyProperty;
/* testVarReadonlyProperty */
var readonly int $varReadonlyProperty;
/* testReadonlyVarProperty */
readonly var int $testReadonlyVarProperty;
/* testStaticReadonlyProperty */
static readonly int $staticReadonlyProperty;
/* testReadonlyStaticProperty */
readonly static int $readonlyStaticProperty;
/* testConstReadonlyProperty */
public readonly const MYCONSTANT = 'foo';
/* testReadonlyPropertyWithoutType */
readonly $propertyWithoutType;
/* testPublicReadonlyProperty */
public readonly int $publicReadonlyProperty;
/* testProtectedReadonlyProperty */
protected readonly int $protectedReadonlyProperty;
/* testPrivateReadonlyProperty */
private readonly int $privateReadonlyProperty;
/* testPublicReadonlyPropertyWithReadonlyFirst */
readonly public int $publicReadonlyProperty;
/* testProtectedReadonlyPropertyWithReadonlyFirst */
readonly protected int $protectedReadonlyProperty;
/* testPrivateReadonlyPropertyWithReadonlyFirst */
readonly private int $privateReadonlyProperty;
/* testReadonlyWithCommentsInDeclaration */
private /* Comment */ readonly /* Comment */ int /* Comment */ $readonlyPropertyWithCommentsInDeclaration;
/* testReadonlyWithNullableProperty */
private readonly ?int $nullableProperty;
/* testReadonlyNullablePropertyWithUnionTypeHintAndNullFirst */
private readonly null|int $nullablePropertyWithUnionTypeHintAndNullFirst;
/* testReadonlyNullablePropertyWithUnionTypeHintAndNullLast */
private readonly int|null $nullablePropertyWithUnionTypeHintAndNullLast;
/* testReadonlyPropertyWithArrayTypeHint */
private readonly array $arrayProperty;
/* testReadonlyPropertyWithSelfTypeHint */
private readonly self $selfProperty;
/* testReadonlyPropertyWithParentTypeHint */
private readonly parent $parentProperty;
/* testReadonlyPropertyWithFullyQualifiedTypeHint */
private readonly \stdClass $propertyWithFullyQualifiedTypeHint;
/* testReadonlyIsCaseInsensitive */
public ReAdOnLy string $caseInsensitiveProperty;
/* testReadonlyConstructorPropertyPromotion */
public function __construct(private readonly bool $constructorPropertyPromotion)
{
}
/* testReadonlyConstructorPropertyPromotionWithReference */
public function __construct(private ReadOnly bool &$constructorPropertyPromotion) {}
}
$anonymousClass = new class () {
/* testReadonlyPropertyInAnonymousClass */
public readonly int $property;
};
class ClassName {
/* testReadonlyUsedAsClassConstantName */
const READONLY = 'readonly';
/* testReadonlyUsedAsMethodName */
public function readonly() {
// Do something.
/* testReadonlyUsedAsPropertyName */
$this->readonly = 'foo';
/* testReadonlyPropertyInTernaryOperator */
$isReadonly = $this->readonly ? true : false;
}
}
/* testReadonlyUsedAsFunctionName */
function readonly()
{
}
/* testReadonlyUsedAsNamespaceName */
namespace Readonly;
/* testReadonlyUsedAsPartOfNamespaceName */
namespace My\Readonly\Collection;
/* testReadonlyAsFunctionCall */
$var = readonly($a, $b);
/* testClassConstantFetchWithReadonlyAsConstantName */
echo ClassName::READONLY;
/* testReadonlyUsedAsFunctionCallWithSpaceBetweenKeywordAndParens */
$var = readonly /* comment */ ();
/* testParseErrorLiveCoding */
// This must be the last test in the file.
readonly
@@ -0,0 +1,236 @@
<?php
/**
* Tests the support of PHP 8.1 "readonly" keyword.
*
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @copyright 2021 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 BackfillReadonlyTest extends AbstractMethodUnitTest
{
/**
* Test that the "readonly" keyword is tokenized as such.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $testContent The token content to look for.
*
* @dataProvider dataReadonly
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReadonly($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_READONLY, T_STRING], $testContent);
$this->assertSame(T_READONLY, $tokens[$target]['code']);
$this->assertSame('T_READONLY', $tokens[$target]['type']);
}//end testReadonly()
/**
* Data provider.
*
* @see testReadonly()
*
* @return array
*/
public function dataReadonly()
{
return [
[
'/* testReadonlyProperty */',
'readonly',
],
[
'/* testVarReadonlyProperty */',
'readonly',
],
[
'/* testReadonlyVarProperty */',
'readonly',
],
[
'/* testStaticReadonlyProperty */',
'readonly',
],
[
'/* testReadonlyStaticProperty */',
'readonly',
],
[
'/* testConstReadonlyProperty */',
'readonly',
],
[
'/* testReadonlyPropertyWithoutType */',
'readonly',
],
[
'/* testPublicReadonlyProperty */',
'readonly',
],
[
'/* testProtectedReadonlyProperty */',
'readonly',
],
[
'/* testPrivateReadonlyProperty */',
'readonly',
],
[
'/* testPublicReadonlyPropertyWithReadonlyFirst */',
'readonly',
],
[
'/* testProtectedReadonlyPropertyWithReadonlyFirst */',
'readonly',
],
[
'/* testPrivateReadonlyPropertyWithReadonlyFirst */',
'readonly',
],
[
'/* testReadonlyWithCommentsInDeclaration */',
'readonly',
],
[
'/* testReadonlyWithNullableProperty */',
'readonly',
],
[
'/* testReadonlyNullablePropertyWithUnionTypeHintAndNullFirst */',
'readonly',
],
[
'/* testReadonlyNullablePropertyWithUnionTypeHintAndNullLast */',
'readonly',
],
[
'/* testReadonlyPropertyWithArrayTypeHint */',
'readonly',
],
[
'/* testReadonlyPropertyWithSelfTypeHint */',
'readonly',
],
[
'/* testReadonlyPropertyWithParentTypeHint */',
'readonly',
],
[
'/* testReadonlyPropertyWithFullyQualifiedTypeHint */',
'readonly',
],
[
'/* testReadonlyIsCaseInsensitive */',
'ReAdOnLy',
],
[
'/* testReadonlyConstructorPropertyPromotion */',
'readonly',
],
[
'/* testReadonlyConstructorPropertyPromotionWithReference */',
'ReadOnly',
],
[
'/* testReadonlyPropertyInAnonymousClass */',
'readonly',
],
[
'/* testReadonlyUsedAsFunctionCallWithSpaceBetweenKeywordAndParens */',
'readonly',
],
[
'/* testParseErrorLiveCoding */',
'readonly',
],
];
}//end dataReadonly()
/**
* Test that "readonly" when not used as the keyword is still tokenized as `T_STRING`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotReadonly
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNotReadonly($testMarker, $testContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_READONLY, T_STRING], $testContent);
$this->assertSame(T_STRING, $tokens[$target]['code']);
$this->assertSame('T_STRING', $tokens[$target]['type']);
}//end testNotReadonly()
/**
* Data provider.
*
* @see testNotReadonly()
*
* @return array
*/
public function dataNotReadonly()
{
return [
[
'/* testReadonlyUsedAsClassConstantName */',
'READONLY',
],
[
'/* testReadonlyUsedAsMethodName */',
'readonly',
],
[
'/* testReadonlyUsedAsPropertyName */',
'readonly',
],
[
'/* testReadonlyPropertyInTernaryOperator */',
'readonly',
],
[
'/* testReadonlyUsedAsFunctionName */',
'readonly',
],
[
'/* testReadonlyUsedAsNamespaceName */',
'Readonly',
],
[
'/* testReadonlyUsedAsPartOfNamespaceName */',
'Readonly',
],
[
'/* testReadonlyAsFunctionCall */',
'readonly',
],
[
'/* testClassConstantFetchWithReadonlyAsConstantName */',
'READONLY',
],
];
}//end dataNotReadonly()
}//end class
@@ -0,0 +1,134 @@
<?php
/*
* Type union or bitwise or.
*/
/* testBitwiseOr1 */
$result = $value | $test /* testBitwiseOr2 */ | $another;
class TypeUnion
{
/* testTypeUnionPropertySimple */
public static Foo|Bar $obj;
/* testTypeUnionPropertyReverseModifierOrder */
static protected int|float $number /* testBitwiseOrPropertyDefaultValue */ = E_WARNING | E_NOTICE;
private
/* testTypeUnionPropertyMulti1 */
array |
/* testTypeUnionPropertyMulti2 */
Traversable | // phpcs:ignore Stnd.Cat.Sniff
false
/* testTypeUnionPropertyMulti3 */
| null $arrayOrFalse;
/* testTypeUnionPropertyNamespaceRelative */
public namespace\Sub\NameA|namespace\Sub\NameB $namespaceRelative;
/* testTypeUnionPropertyPartiallyQualified */
public Partially\Qualified\NameA|Partially\Qualified\NameB $partiallyQual;
/* testTypeUnionPropertyFullyQualified */
public \Fully\Qualified\NameA|\Fully\Qualified\NameB $fullyQual;
/* testTypeUnionPropertyWithReadOnlyKeyword */
protected readonly string|null $array;
/* testTypeUnionPropertyWithStaticAndReadOnlyKeywords */
static readonly string|null $array;
/* testTypeUnionPropertyWithVarAndReadOnlyKeywords */
var readonly string|null $array;
/* testTypeUnionPropertyWithReadOnlyKeywordFirst */
readonly protected string|null $array;
/* testTypeUnionPropertyWithOnlyReadOnlyKeyword */
readonly string|null $nullableString;
public function paramTypes(
/* testTypeUnionParam1 */
int|float $paramA /* testBitwiseOrParamDefaultValue */ = CONSTANT_A | CONSTANT_B,
/* testTypeUnionParam2 */
Foo|\Bar /* testTypeUnionParam3 */ |Baz &...$paramB = null,
) {
/* testBitwiseOr3 */
return (($a1 ^ $b1) |($a2 ^ $b2)) + $c;
}
public function identifierNames(
/* testTypeUnionParamNamespaceRelative */
namespace\Sub\NameA|namespace\Sub\NameB $paramA,
/* testTypeUnionParamPartiallyQualified */
Partially\Qualified\NameA|Partially\Qualified\NameB $paramB,
/* testTypeUnionParamFullyQualified */
\Fully\Qualified\NameA|\Fully\Qualified\NameB $paramC,
) {}
/* testTypeUnionReturnType */
public function returnType() : int|false {}
/* testTypeUnionConstructorPropertyPromotion */
public function __construct( public bool|null $property) {}
/* testTypeUnionAbstractMethodReturnType1 */
abstract public function abstractMethod(): object|array /* testTypeUnionAbstractMethodReturnType2 */ |false;
/* testTypeUnionReturnTypeNamespaceRelative */
public function identifierNamesReturnRelative() : namespace\Sub\NameA|namespace\Sub\NameB {}
/* testTypeUnionReturnPartiallyQualified */
public function identifierNamesReturnPQ() : Partially\Qualified\NameA|Partially\Qualified\NameB {}
/* testTypeUnionReturnFullyQualified */
public function identifierNamesReturnFQ() : \Fully\Qualified\NameA|\Fully\Qualified\NameB {}
}
/* testTypeUnionClosureParamIllegalNullable */
$closureWithParamType = function (?string|null $string) {};
function globalFunctionWithSpreadAndReference(
/* testTypeUnionWithReference */
float|null &$paramA,
/* testTypeUnionWithSpreadOperator */
string|int ...$paramB
) {}
/* testBitwiseOrClosureParamDefault */
$closureWithReturnType = function ($string = NONSENSE | FAKE)/* testTypeUnionClosureReturn */ : \Package\MyA|PackageB {};
/* testTypeUnionArrowParam */
$arrowWithParamType = fn (object|array $param, /* testBitwiseOrArrowParamDefault */ ?int $int = CONSTA | CONSTB )
/* testBitwiseOrArrowExpression */
=> $param | $int;
/* testTypeUnionArrowReturnType */
$arrowWithReturnType = fn ($param) : int|null => $param * 10;
/* testBitwiseOrInArrayKey */
$array = array(
A | B => /* testBitwiseOrInArrayValue */ B | C
);
/* testBitwiseOrInShortArrayKey */
$array = [
A | B => /* testBitwiseOrInShortArrayValue */ B | C
];
/* testBitwiseOrTryCatch */
try {
} catch ( ExceptionA | ExceptionB $e ) {
}
/* testBitwiseOrNonArrowFnFunctionCall */
$obj->fn($something | $else);
/* testTypeUnionNonArrowFunctionDeclaration */
function &fn(int|false $something) {}
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
return function( type|
@@ -0,0 +1,138 @@
<?php
/**
* Tests the conversion of bitwise or tokens to type union tokens.
*
* @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 BitwiseOrTest extends AbstractMethodUnitTest
{
/**
* Test that non-union type bitwise or tokens are still tokenized as bitwise or.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataBitwiseOr
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testBitwiseOr($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_BITWISE_OR, T_TYPE_UNION]);
$this->assertSame(T_BITWISE_OR, $tokens[$opener]['code']);
$this->assertSame('T_BITWISE_OR', $tokens[$opener]['type']);
}//end testBitwiseOr()
/**
* Data provider.
*
* @see testBitwiseOr()
*
* @return array
*/
public function dataBitwiseOr()
{
return [
['/* testBitwiseOr1 */'],
['/* testBitwiseOr2 */'],
['/* testBitwiseOrPropertyDefaultValue */'],
['/* testBitwiseOrParamDefaultValue */'],
['/* testBitwiseOr3 */'],
['/* testBitwiseOrClosureParamDefault */'],
['/* testBitwiseOrArrowParamDefault */'],
['/* testBitwiseOrArrowExpression */'],
['/* testBitwiseOrInArrayKey */'],
['/* testBitwiseOrInArrayValue */'],
['/* testBitwiseOrInShortArrayKey */'],
['/* testBitwiseOrInShortArrayValue */'],
['/* testBitwiseOrTryCatch */'],
['/* testBitwiseOrNonArrowFnFunctionCall */'],
['/* testLiveCoding */'],
];
}//end dataBitwiseOr()
/**
* Test that bitwise or tokens when used as part of a union type are tokenized as `T_TYPE_UNION`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataTypeUnion
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testTypeUnion($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_BITWISE_OR, T_TYPE_UNION]);
$this->assertSame(T_TYPE_UNION, $tokens[$opener]['code']);
$this->assertSame('T_TYPE_UNION', $tokens[$opener]['type']);
}//end testTypeUnion()
/**
* Data provider.
*
* @see testTypeUnion()
*
* @return array
*/
public function dataTypeUnion()
{
return [
['/* testTypeUnionPropertySimple */'],
['/* testTypeUnionPropertyReverseModifierOrder */'],
['/* testTypeUnionPropertyMulti1 */'],
['/* testTypeUnionPropertyMulti2 */'],
['/* testTypeUnionPropertyMulti3 */'],
['/* testTypeUnionPropertyNamespaceRelative */'],
['/* testTypeUnionPropertyPartiallyQualified */'],
['/* testTypeUnionPropertyFullyQualified */'],
['/* testTypeUnionPropertyWithReadOnlyKeyword */'],
['/* testTypeUnionPropertyWithReadOnlyKeywordFirst */'],
['/* testTypeUnionPropertyWithStaticAndReadOnlyKeywords */'],
['/* testTypeUnionPropertyWithVarAndReadOnlyKeywords */'],
['/* testTypeUnionPropertyWithOnlyReadOnlyKeyword */'],
['/* testTypeUnionParam1 */'],
['/* testTypeUnionParam2 */'],
['/* testTypeUnionParam3 */'],
['/* testTypeUnionParamNamespaceRelative */'],
['/* testTypeUnionParamPartiallyQualified */'],
['/* testTypeUnionParamFullyQualified */'],
['/* testTypeUnionReturnType */'],
['/* testTypeUnionConstructorPropertyPromotion */'],
['/* testTypeUnionAbstractMethodReturnType1 */'],
['/* testTypeUnionAbstractMethodReturnType2 */'],
['/* testTypeUnionReturnTypeNamespaceRelative */'],
['/* testTypeUnionReturnPartiallyQualified */'],
['/* testTypeUnionReturnFullyQualified */'],
['/* testTypeUnionClosureParamIllegalNullable */'],
['/* testTypeUnionWithReference */'],
['/* testTypeUnionWithSpreadOperator */'],
['/* testTypeUnionClosureReturn */'],
['/* testTypeUnionArrowParam */'],
['/* testTypeUnionArrowReturnType */'],
['/* testTypeUnionNonArrowFunctionDeclaration */'],
];
}//end dataTypeUnion()
}//end class
@@ -0,0 +1,227 @@
<?php
/* testNamespaceIsKeyword */
namespace SomeNamespace;
class ContextSensitiveKeywords
{
const /* testAbstract */ ABSTRACT = 'ABSTRACT';
const /* testArray */ ARRAY = 'ARRAY';
const /* testAs */ AS = 'AS';
const /* testBreak */ BREAK = 'BREAK';
const /* testCallable */ CALLABLE = 'CALLABLE';
const /* testCase */ CASE = 'CASE';
const /* testCatch */ CATCH = 'CATCH';
const /* testClass */ CLASS = 'CLASS';
const /* testClone */ CLONE = 'CLONE';
const /* testConst */ CONST = 'CONST';
const /* testContinue */ CONTINUE = 'CONTINUE';
const /* testDeclare */ DECLARE = 'DECLARE';
const /* testDefault */ DEFAULT = 'DEFAULT';
const /* testDo */ DO = 'DO';
const /* testEcho */ ECHO = 'ECHO';
const /* testElse */ ELSE = 'ELSE';
const /* testElseIf */ ELSEIF = 'ELSEIF';
const /* testEmpty */ EMPTY = 'EMPTY';
const /* testEndDeclare */ ENDDECLARE = 'ENDDECLARE';
const /* testEndFor */ ENDFOR = 'ENDFOR';
const /* testEndForeach */ ENDFOREACH = 'ENDFOREACH';
const /* testEndIf */ ENDIF = 'ENDIF';
const /* testEndSwitch */ ENDSWITCH = 'ENDSWITCH';
const /* testEndWhile */ ENDWHILE = 'ENDWHILE';
const /* testEnum */ ENUM = 'ENUM';
const /* testEval */ EVAL = 'EVAL';
const /* testExit */ EXIT = 'EXIT';
const /* testExtends */ EXTENDS = 'EXTENDS';
const /* testFinal */ FINAL = 'FINAL';
const /* testFinally */ FINALLY = 'FINALLY';
const /* testFn */ FN = 'FN';
const /* testFor */ FOR = 'FOR';
const /* testForeach */ FOREACH = 'FOREACH';
const /* testFunction */ FUNCTION = 'FUNCTION';
const /* testGlobal */ GLOBAL = 'GLOBAL';
const /* testGoto */ GOTO = 'GOTO';
const /* testIf */ IF = 'IF';
const /* testImplements */ IMPLEMENTS = 'IMPLEMENTS';
const /* testInclude */ INCLUDE = 'INCLUDE';
const /* testIncludeOnce */ INCLUDE_ONCE = 'INCLUDE_ONCE';
const /* testInstanceOf */ INSTANCEOF = 'INSTANCEOF';
const /* testInsteadOf */ INSTEADOF = 'INSTEADOF';
const /* testInterface */ INTERFACE = 'INTERFACE';
const /* testIsset */ ISSET = 'ISSET';
const /* testList */ LIST = 'LIST';
const /* testMatch */ MATCH = 'MATCH';
const /* testNamespace */ NAMESPACE = 'NAMESPACE';
const /* testNew */ NEW = 'NEW';
const /* testParent */ PARENT = 'PARENT';
const /* testPrint */ PRINT = 'PRINT';
const /* testPrivate */ PRIVATE = 'PRIVATE';
const /* testProtected */ PROTECTED = 'PROTECTED';
const /* testPublic */ PUBLIC = 'PUBLIC';
const /* testReadonly */ READONLY = 'READONLY';
const /* testRequire */ REQUIRE = 'REQUIRE';
const /* testRequireOnce */ REQUIRE_ONCE = 'REQUIRE_ONCE';
const /* testReturn */ RETURN = 'RETURN';
const /* testSelf */ SELF = 'SELF';
const /* testStatic */ STATIC = 'STATIC';
const /* testSwitch */ SWITCH = 'SWITCH';
const /* testThrows */ THROW = 'THROW';
const /* testTrait */ TRAIT = 'TRAIT';
const /* testTry */ TRY = 'TRY';
const /* testUnset */ UNSET = 'UNSET';
const /* testUse */ USE = 'USE';
const /* testVar */ VAR = 'VAR';
const /* testWhile */ WHILE = 'WHILE';
const /* testYield */ YIELD = 'YIELD';
const /* testYieldFrom */ YIELD_FROM = 'YIELD_FROM';
const /* testAnd */ AND = 'LOGICAL_AND';
const /* testOr */ OR = 'LOGICAL_OR';
const /* testXor */ XOR = 'LOGICAL_XOR';
}
namespace /* testKeywordAfterNamespaceShouldBeString */ class;
namespace /* testNamespaceNameIsString1 */ my\ /* testNamespaceNameIsString2 */ class\ /* testNamespaceNameIsString3 */ foreach;
/* testAbstractIsKeyword */ abstract /* testClassIsKeyword */ class SomeClass
/* testExtendsIsKeyword */ extends SomeParent
/* testImplementsIsKeyword */ implements SomeInterface
{
/* testUseIsKeyword */ use SomeTrait, OtherTrait {
SomeTrait::a /* testInsteadOfIsKeyword */ insteadof A;
OtherTrait::a /* testAsIsKeyword */ as B;
}
/* testConstIsKeyword */ const CONSTANT = 'c';
/* testPrivateIsKeyword */ private $private;
/* testProtectedIsKeyword */ protected $protected;
/* testPublicIsKeyword */ public $public;
/* testVarIsKeyword */ var $var;
/* testStaticIsKeyword */ static $static;
/* testReadonlyIsKeyword */ readonly $readonly;
/* testFinalIsKeyword */ final /* testFunctionIsKeyword */ function someFunction(
/* testCallableIsKeyword */
callable $callable,
/* testSelfIsKeyword */
self $self,
/* testParentIsKeyword */
parent $parent
) {
/* testReturnIsKeyword */
return $this;
}
}
/* testInterfaceIsKeyword */ interface SomeInterface {}
/* testTraitIsKeyword */ trait SomeTrait {}
/* testEnumIsKeyword */ enum SomeEnum {}
$object = /* testNewIsKeyword */ new SomeClass();
$object /* testInstanceOfIsKeyword */ instanceof SomeClass;
$copy = /* testCloneIsKeyword */ clone $object;
/* testIfIsKeyword */ if (/* testEmptyIsKeyword */ empty($a)):
/* testElseIfIsKeyword */ elseif (false):
/* testElseIsKeyword */ else:
/* testEndIfIsKeyword */ endif;
/* testForIsKeyword */ for ($i = 0; $i < 100; $i++):
/* testEndForIsKeyword */ endfor;
/* testForeachIsKeyword */ foreach ($array as $value):
/* testEndForeachIsKeyword */ endforeach;
/* testSwitchIsKeyword */ switch (true):
/* testCaseIsKeyword */case false:
/* testBreakIsKeyword */
break;
/* testDefaultIsKeyword */ default:
/* testContinueIsKeyword */
continue;
/* testEndSwitchIsKeyword */ endswitch;
/* testDoIsKeyword */ do {
} while (false);
/* testWhileIsKeyword */ while (false):
/* testEndWhileIsKeyword */ endwhile;
/* testTryIsKeyword */
try {
/* testThrowIsKeyword */
throw new Exception();
} /* testCatchIsKeyword */ catch (Exception $e) {
} /* testFinallyIsKeyword */ finally {
}
/* testGlobalIsKeyword */
global $foo;
/* testEchoIsKeyword */
echo $foo;
/* testPrintIsKeyword */
print $foo;
/* testDieIsKeyword */
die($foo);
/* testEvalIsKeyword */
eval('<?php echo 5;');
/* testExitIsKeyword */
exit;
/* testIssetIsKeyword */
$a = isset($a);
/* testUnsetIsKeyword */
unset($a);
/* testIncludeIsKeyword */
include 'file.php';
/* testIncludeOnceIsKeyword */
include_once 'file.php';
/* testRequireIsKeyword */
require 'file.php';
/* testRequireOnceIsKeyword */
require_once 'file.php';
[$a, $b] = /* testListIsKeyword */ list($array);
/* testGotoIsKeyword */
goto label;
$match = /* testMatchIsKeyword */ match ($test) {
1 => 'a',
2 => 'b',
/* testMatchDefaultIsKeyword */ default => 'default',
};
$closure = /* testFnIsKeyword */ fn () => 'string';
function () {
/* testYieldIsKeyword */ yield $f;
/* testYieldFromIsKeyword */ yield from someFunction();
};
/* testDeclareIsKeyword */ declare(ticks=1):
/* testEndDeclareIsKeyword */ enddeclare;
if (true /* testAndIsKeyword */ and false /* testOrIsKeyword */ or null /* testXorIsKeyword */ xor 0) {
}
$anonymousClass = new /* testAnonymousClassIsKeyword */ class {};
$anonymousClass2 = new class /* testExtendsInAnonymousClassIsKeyword */ extends SomeParent {};
$anonymousClass3 = new class /* testImplementsInAnonymousClassIsKeyword */ implements SomeInterface {};
$instantiated1 = new /* testClassInstantiationParentIsKeyword */ parent();
$instantiated2 = new /* testClassInstantiationSelfIsKeyword */ SELF;
$instantiated3 = new /* testClassInstantiationStaticIsKeyword */ static($param);
class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception
{}
function /* testKeywordAfterFunctionShouldBeString */ eval() {}
function /* testKeywordAfterFunctionByRefShouldBeString */ &switch() {}
@@ -0,0 +1,509 @@
<?php
/**
* Tests the conversion of context sensitive keywords to T_STRING.
*
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @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;
use PHP_CodeSniffer\Util\Tokens;
class ContextSensitiveKeywordsTest extends AbstractMethodUnitTest
{
/**
* Test that context sensitive keyword is tokenized as string when it should be string.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataStrings
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testStrings($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_STRING]));
$this->assertSame(T_STRING, $tokens[$token]['code']);
$this->assertSame('T_STRING', $tokens[$token]['type']);
}//end testStrings()
/**
* Data provider.
*
* @see testStrings()
*
* @return array
*/
public function dataStrings()
{
return [
['/* testAbstract */'],
['/* testArray */'],
['/* testAs */'],
['/* testBreak */'],
['/* testCallable */'],
['/* testCase */'],
['/* testCatch */'],
['/* testClass */'],
['/* testClone */'],
['/* testConst */'],
['/* testContinue */'],
['/* testDeclare */'],
['/* testDefault */'],
['/* testDo */'],
['/* testEcho */'],
['/* testElse */'],
['/* testElseIf */'],
['/* testEmpty */'],
['/* testEndDeclare */'],
['/* testEndFor */'],
['/* testEndForeach */'],
['/* testEndIf */'],
['/* testEndSwitch */'],
['/* testEndWhile */'],
['/* testEnum */'],
['/* testEval */'],
['/* testExit */'],
['/* testExtends */'],
['/* testFinal */'],
['/* testFinally */'],
['/* testFn */'],
['/* testFor */'],
['/* testForeach */'],
['/* testFunction */'],
['/* testGlobal */'],
['/* testGoto */'],
['/* testIf */'],
['/* testImplements */'],
['/* testInclude */'],
['/* testIncludeOnce */'],
['/* testInstanceOf */'],
['/* testInsteadOf */'],
['/* testInterface */'],
['/* testIsset */'],
['/* testList */'],
['/* testMatch */'],
['/* testNamespace */'],
['/* testNew */'],
['/* testParent */'],
['/* testPrint */'],
['/* testPrivate */'],
['/* testProtected */'],
['/* testPublic */'],
['/* testReadonly */'],
['/* testRequire */'],
['/* testRequireOnce */'],
['/* testReturn */'],
['/* testSelf */'],
['/* testStatic */'],
['/* testSwitch */'],
['/* testThrows */'],
['/* testTrait */'],
['/* testTry */'],
['/* testUnset */'],
['/* testUse */'],
['/* testVar */'],
['/* testWhile */'],
['/* testYield */'],
['/* testYieldFrom */'],
['/* testAnd */'],
['/* testOr */'],
['/* testXor */'],
['/* testKeywordAfterNamespaceShouldBeString */'],
['/* testNamespaceNameIsString1 */'],
['/* testNamespaceNameIsString2 */'],
['/* testNamespaceNameIsString3 */'],
['/* testKeywordAfterFunctionShouldBeString */'],
['/* testKeywordAfterFunctionByRefShouldBeString */'],
];
}//end dataStrings()
/**
* Test that context sensitive keyword is tokenized as keyword when it should be keyword.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expectedTokenType The expected token type.
*
* @dataProvider dataKeywords
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testKeywords($testMarker, $expectedTokenType)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, (Tokens::$contextSensitiveKeywords + [T_ANON_CLASS, T_MATCH_DEFAULT, T_PARENT, T_SELF, T_STRING]));
$this->assertSame(constant($expectedTokenType), $tokens[$token]['code']);
$this->assertSame($expectedTokenType, $tokens[$token]['type']);
}//end testKeywords()
/**
* Data provider.
*
* @see testKeywords()
*
* @return array
*/
public function dataKeywords()
{
return [
[
'/* testNamespaceIsKeyword */',
'T_NAMESPACE',
],
[
'/* testAbstractIsKeyword */',
'T_ABSTRACT',
],
[
'/* testClassIsKeyword */',
'T_CLASS',
],
[
'/* testExtendsIsKeyword */',
'T_EXTENDS',
],
[
'/* testImplementsIsKeyword */',
'T_IMPLEMENTS',
],
[
'/* testUseIsKeyword */',
'T_USE',
],
[
'/* testInsteadOfIsKeyword */',
'T_INSTEADOF',
],
[
'/* testAsIsKeyword */',
'T_AS',
],
[
'/* testConstIsKeyword */',
'T_CONST',
],
[
'/* testPrivateIsKeyword */',
'T_PRIVATE',
],
[
'/* testProtectedIsKeyword */',
'T_PROTECTED',
],
[
'/* testPublicIsKeyword */',
'T_PUBLIC',
],
[
'/* testVarIsKeyword */',
'T_VAR',
],
[
'/* testStaticIsKeyword */',
'T_STATIC',
],
[
'/* testReadonlyIsKeyword */',
'T_READONLY',
],
[
'/* testFinalIsKeyword */',
'T_FINAL',
],
[
'/* testFunctionIsKeyword */',
'T_FUNCTION',
],
[
'/* testCallableIsKeyword */',
'T_CALLABLE',
],
[
'/* testSelfIsKeyword */',
'T_SELF',
],
[
'/* testParentIsKeyword */',
'T_PARENT',
],
[
'/* testReturnIsKeyword */',
'T_RETURN',
],
[
'/* testInterfaceIsKeyword */',
'T_INTERFACE',
],
[
'/* testTraitIsKeyword */',
'T_TRAIT',
],
[
'/* testEnumIsKeyword */',
'T_ENUM',
],
[
'/* testNewIsKeyword */',
'T_NEW',
],
[
'/* testInstanceOfIsKeyword */',
'T_INSTANCEOF',
],
[
'/* testCloneIsKeyword */',
'T_CLONE',
],
[
'/* testIfIsKeyword */',
'T_IF',
],
[
'/* testEmptyIsKeyword */',
'T_EMPTY',
],
[
'/* testElseIfIsKeyword */',
'T_ELSEIF',
],
[
'/* testElseIsKeyword */',
'T_ELSE',
],
[
'/* testEndIfIsKeyword */',
'T_ENDIF',
],
[
'/* testForIsKeyword */',
'T_FOR',
],
[
'/* testEndForIsKeyword */',
'T_ENDFOR',
],
[
'/* testForeachIsKeyword */',
'T_FOREACH',
],
[
'/* testEndForeachIsKeyword */',
'T_ENDFOREACH',
],
[
'/* testSwitchIsKeyword */',
'T_SWITCH',
],
[
'/* testCaseIsKeyword */',
'T_CASE',
],
[
'/* testDefaultIsKeyword */',
'T_DEFAULT',
],
[
'/* testEndSwitchIsKeyword */',
'T_ENDSWITCH',
],
[
'/* testBreakIsKeyword */',
'T_BREAK',
],
[
'/* testContinueIsKeyword */',
'T_CONTINUE',
],
[
'/* testDoIsKeyword */',
'T_DO',
],
[
'/* testWhileIsKeyword */',
'T_WHILE',
],
[
'/* testEndWhileIsKeyword */',
'T_ENDWHILE',
],
[
'/* testTryIsKeyword */',
'T_TRY',
],
[
'/* testThrowIsKeyword */',
'T_THROW',
],
[
'/* testCatchIsKeyword */',
'T_CATCH',
],
[
'/* testFinallyIsKeyword */',
'T_FINALLY',
],
[
'/* testGlobalIsKeyword */',
'T_GLOBAL',
],
[
'/* testEchoIsKeyword */',
'T_ECHO',
],
[
'/* testPrintIsKeyword */',
'T_PRINT',
],
[
'/* testDieIsKeyword */',
'T_EXIT',
],
[
'/* testEvalIsKeyword */',
'T_EVAL',
],
[
'/* testExitIsKeyword */',
'T_EXIT',
],
[
'/* testIssetIsKeyword */',
'T_ISSET',
],
[
'/* testUnsetIsKeyword */',
'T_UNSET',
],
[
'/* testIncludeIsKeyword */',
'T_INCLUDE',
],
[
'/* testIncludeOnceIsKeyword */',
'T_INCLUDE_ONCE',
],
[
'/* testRequireIsKeyword */',
'T_REQUIRE',
],
[
'/* testRequireOnceIsKeyword */',
'T_REQUIRE_ONCE',
],
[
'/* testListIsKeyword */',
'T_LIST',
],
[
'/* testGotoIsKeyword */',
'T_GOTO',
],
[
'/* testMatchIsKeyword */',
'T_MATCH',
],
[
'/* testMatchDefaultIsKeyword */',
'T_MATCH_DEFAULT',
],
[
'/* testFnIsKeyword */',
'T_FN',
],
[
'/* testYieldIsKeyword */',
'T_YIELD',
],
[
'/* testYieldFromIsKeyword */',
'T_YIELD_FROM',
],
[
'/* testDeclareIsKeyword */',
'T_DECLARE',
],
[
'/* testEndDeclareIsKeyword */',
'T_ENDDECLARE',
],
[
'/* testAndIsKeyword */',
'T_LOGICAL_AND',
],
[
'/* testOrIsKeyword */',
'T_LOGICAL_OR',
],
[
'/* testXorIsKeyword */',
'T_LOGICAL_XOR',
],
[
'/* testAnonymousClassIsKeyword */',
'T_ANON_CLASS',
],
[
'/* testExtendsInAnonymousClassIsKeyword */',
'T_EXTENDS',
],
[
'/* testImplementsInAnonymousClassIsKeyword */',
'T_IMPLEMENTS',
],
[
'/* testClassInstantiationParentIsKeyword */',
'T_PARENT',
],
[
'/* testClassInstantiationSelfIsKeyword */',
'T_SELF',
],
[
'/* testClassInstantiationStaticIsKeyword */',
'T_STATIC',
],
[
'/* testNamespaceInNameIsKeyword */',
'T_NAMESPACE',
],
];
}//end dataKeywords()
}//end class
@@ -0,0 +1,203 @@
<?php
function matchWithDefault($i) {
return match ($i) {
1 => 1,
2 => 2,
/* testSimpleMatchDefault */
default => 'default',
};
}
function switchWithDefault($i) {
switch ($i) {
case 1:
return 1;
case 2:
return 2;
/* testSimpleSwitchDefault */
default:
return 'default';
}
}
function switchWithDefaultAndCurlies($i) {
switch ($i) {
case 1:
return 1;
case 2:
return 2;
/* testSimpleSwitchDefaultWithCurlies */
default: {
return 'default';
}
}
}
function matchWithDefaultInSwitch() {
switch ($something) {
case 'foo':
$var = [1, 2, 3];
$var = match ($i) {
1 => 1,
/* testMatchDefaultNestedInSwitchCase1 */
default => 'default',
};
continue;
case 'bar' :
$i = callMe($a, $b);
return match ($i) {
1 => 1,
/* testMatchDefaultNestedInSwitchCase2 */
default => 'default',
};
/* testSwitchDefault */
default;
echo 'something', match ($i) {
1, => 1,
/* testMatchDefaultNestedInSwitchDefault */
default, => 'default',
};
break;
}
}
function switchWithDefaultInMatch() {
$x = match ($y) {
5, 8 => function($z) {
switch($z) {
case 'a';
$var = [1, 2, 3];
return 'a';
/* testSwitchDefaultNestedInMatchCase */
default:
$var = [1, 2, 3];
return 'default1';
}
},
/* testMatchDefault */
default => function($z) {
switch($z) {
case 'a':
$i = callMe($a, $b);
return 'b';
/* testSwitchDefaultNestedInMatchDefault */
default:
$i = callMe($a, $b);
return 'default2';
}
}
};
}
function shortArrayWithConstantKey() {
$arr = [
/* testClassConstantAsShortArrayKey */
SomeClass::DEFAULT => 1,
/* testClassPropertyAsShortArrayKey */
SomeClass->DEFAULT => 1,
/* testNamespacedConstantAsShortArrayKey */
// Intentional parse error PHP < 8.0. Reserved keyword used as namespaced constant.
SomeNamespace\DEFAULT => 1,
/* testFQNGlobalConstantAsShortArrayKey */
// Intentional parse error in PHP < 8.0. Reserved keyword used as global constant.
\DEFAULT => 1,
];
}
function longArrayWithConstantKey() {
$arr = array(
/* testClassConstantAsLongArrayKey */
SomeClass::DEFAULT => 1,
);
}
function yieldWithConstantKey() {
/* testClassConstantAsYieldKey */
yield SomeClass::DEFAULT => 1;
}
function longArrayWithConstantKeyNestedInMatch() {
return match($x) {
/* testMatchDefaultWithNestedLongArrayWithClassConstantKey */
DEFAULT => array(
/* testClassConstantAsLongArrayKeyNestedInMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchDefaultWithNestedLongArrayWithClassConstantKeyLevelDown */
DEFAULT => array(
/* testClassConstantAsLongArrayKeyNestedInMatchLevelDown */
SomeClass::DEFAULT => 1,
),
},
),
};
}
function shortArrayWithConstantKeyNestedInMatch() {
return match($x) {
/* testMatchDefaultWithNestedShortArrayWithClassConstantKey */
DEFAULT => [
/* testClassConstantAsShortArrayKeyNestedInMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchDefaultWithNestedShortArrayWithClassConstantKeyLevelDown */
DEFAULT => [
/* testClassConstantAsShortArrayKeyNestedInMatchLevelDown */
SomeClass::DEFAULT => 1,
],
},
],
};
}
function longArrayWithConstantKeyWithNestedMatch() {
return array(
/* testClassConstantAsLongArrayKeyWithNestedMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchDefaultNestedInLongArray */
DEFAULT => 'foo'
},
);
}
function shortArrayWithConstantKeyWithNestedMatch() {
return [
/* testClassConstantAsShortArrayKeyWithNestedMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchDefaultNestedInShortArray */
DEFAULT => 'foo'
},
];
}
function switchWithConstantNonDefault($i) {
switch ($i) {
/* testClassConstantInSwitchCase */
case SomeClass::DEFAULT:
return 1;
/* testClassPropertyInSwitchCase */
case SomeClass->DEFAULT:
return 2;
/* testNamespacedConstantInSwitchCase */
// Intentional parse error PHP < 8.0. Reserved keyword used as constant.
case SomeNamespace\DEFAULT:
return 2;
/* testNamespaceRelativeConstantInSwitchCase */
// Intentional parse error PHP < 8.0. Reserved keyword used as global constant.
case namespace\DEFAULT:
return 2;
}
}
class Foo {
/* testClassConstant */
const DEFAULT = 'foo';
/* testMethodDeclaration */
public function default() {}
}
@@ -0,0 +1,302 @@
<?php
/**
* Tests the retokenization of the `default` keyword to T_MATCH_DEFAULT for PHP 8.0 match structures
* and makes sure that the tokenization of switch `T_DEFAULT` structures is not aversely affected.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2020-2021 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 DefaultKeywordTest extends AbstractMethodUnitTest
{
/**
* Test the retokenization of the `default` keyword for match structure to `T_MATCH_DEFAULT`.
*
* Note: Cases and default structures within a match structure do *NOT* get case/default scope
* conditions, in contrast to case and default structures in switch control structures.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to look for.
*
* @dataProvider dataMatchDefault
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testMatchDefault($testMarker, $testContent='default')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_MATCH_DEFAULT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_DEFAULT (code)');
$this->assertSame('T_MATCH_DEFAULT', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_DEFAULT (type)');
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
}//end testMatchDefault()
/**
* Data provider.
*
* @see testMatchDefault()
*
* @return array
*/
public function dataMatchDefault()
{
return [
'simple_match_default' => ['/* testSimpleMatchDefault */'],
'match_default_in_switch_case_1' => ['/* testMatchDefaultNestedInSwitchCase1 */'],
'match_default_in_switch_case_2' => ['/* testMatchDefaultNestedInSwitchCase2 */'],
'match_default_in_switch_default' => ['/* testMatchDefaultNestedInSwitchDefault */'],
'match_default_containing_switch' => ['/* testMatchDefault */'],
'match_default_with_nested_long_array_and_default_key' => [
'/* testMatchDefaultWithNestedLongArrayWithClassConstantKey */',
'DEFAULT',
],
'match_default_with_nested_long_array_and_default_key_2' => [
'/* testMatchDefaultWithNestedLongArrayWithClassConstantKeyLevelDown */',
'DEFAULT',
],
'match_default_with_nested_short_array_and_default_key' => [
'/* testMatchDefaultWithNestedShortArrayWithClassConstantKey */',
'DEFAULT',
],
'match_default_with_nested_short_array_and_default_key_2' => [
'/* testMatchDefaultWithNestedShortArrayWithClassConstantKeyLevelDown */',
'DEFAULT',
],
'match_default_in_long_array' => [
'/* testMatchDefaultNestedInLongArray */',
'DEFAULT',
],
'match_default_in_short_array' => [
'/* testMatchDefaultNestedInShortArray */',
'DEFAULT',
],
];
}//end dataMatchDefault()
/**
* Verify that the retokenization of `T_DEFAULT` tokens in match constructs, doesn't negatively
* impact the tokenization of `T_DEFAULT` tokens in switch control structures.
*
* Note: Cases and default structures within a switch control structure *do* get case/default scope
* conditions.
*
* @param string $testMarker The comment prefacing the target token.
* @param int $openerOffset The expected offset of the scope opener in relation to the testMarker.
* @param int $closerOffset The expected offset of the scope closer in relation to the testMarker.
* @param int|null $conditionStop The expected offset at which tokens stop having T_DEFAULT as a scope condition.
* @param string $testContent The token content to look for.
*
* @dataProvider dataSwitchDefault
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testSwitchDefault($testMarker, $openerOffset, $closerOffset, $conditionStop=null, $testContent='default')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$expectedScopeOpener = ($token + $openerOffset);
$expectedScopeCloser = ($token + $closerOffset);
$this->assertSame(T_DEFAULT, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_DEFAULT (code)');
$this->assertSame('T_DEFAULT', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_DEFAULT (type)');
$this->assertArrayHasKey('scope_condition', $tokenArray, 'Scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokenArray, 'Scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokenArray, 'Scope closer is not set');
$this->assertSame($token, $tokenArray['scope_condition'], 'Scope condition is not the T_DEFAULT token');
$this->assertSame($expectedScopeOpener, $tokenArray['scope_opener'], 'Scope opener of the T_DEFAULT token incorrect');
$this->assertSame($expectedScopeCloser, $tokenArray['scope_closer'], 'Scope closer of the T_DEFAULT token incorrect');
$opener = $tokenArray['scope_opener'];
$this->assertArrayHasKey('scope_condition', $tokens[$opener], 'Opener scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Opener scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Opener scope closer is not set');
$this->assertSame($token, $tokens[$opener]['scope_condition'], 'Opener scope condition is not the T_DEFAULT token');
$this->assertSame($expectedScopeOpener, $tokens[$opener]['scope_opener'], 'T_DEFAULT opener scope opener token incorrect');
$this->assertSame($expectedScopeCloser, $tokens[$opener]['scope_closer'], 'T_DEFAULT opener scope closer token incorrect');
$closer = $tokenArray['scope_closer'];
$this->assertArrayHasKey('scope_condition', $tokens[$closer], 'Closer scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Closer scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Closer scope closer is not set');
$this->assertSame($token, $tokens[$closer]['scope_condition'], 'Closer scope condition is not the T_DEFAULT token');
$this->assertSame($expectedScopeOpener, $tokens[$closer]['scope_opener'], 'T_DEFAULT closer scope opener token incorrect');
$this->assertSame($expectedScopeCloser, $tokens[$closer]['scope_closer'], 'T_DEFAULT closer scope closer token incorrect');
if (($opener + 1) !== $closer) {
$end = $closer;
if (isset($conditionStop) === true) {
$end = $conditionStop;
}
for ($i = ($opener + 1); $i < $end; $i++) {
$this->assertArrayHasKey(
$token,
$tokens[$i]['conditions'],
'T_DEFAULT condition not added for token belonging to the T_DEFAULT structure'
);
}
}
}//end testSwitchDefault()
/**
* Data provider.
*
* @see testSwitchDefault()
*
* @return array
*/
public function dataSwitchDefault()
{
return [
'simple_switch_default' => [
'/* testSimpleSwitchDefault */',
1,
4,
],
'simple_switch_default_with_curlies' => [
// For a default structure with curly braces, the scope opener
// will be the open curly and the closer the close curly.
// However, scope conditions will not be set for open to close,
// but only for the open token up to the "break/return/continue" etc.
'/* testSimpleSwitchDefaultWithCurlies */',
3,
12,
6,
],
'switch_default_toplevel' => [
'/* testSwitchDefault */',
1,
43,
],
'switch_default_nested_in_match_case' => [
'/* testSwitchDefaultNestedInMatchCase */',
1,
20,
],
'switch_default_nested_in_match_default' => [
'/* testSwitchDefaultNestedInMatchDefault */',
1,
18,
],
];
}//end dataSwitchDefault()
/**
* Verify that the retokenization of `T_DEFAULT` tokens in match constructs, doesn't negatively
* impact the tokenization of `T_STRING` tokens with the contents 'default' which aren't in
* actual fact the default keyword.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $testContent The token content to look for.
*
* @dataProvider dataNotDefaultKeyword
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNotDefaultKeyword($testMarker, $testContent='DEFAULT')
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_MATCH_DEFAULT, T_DEFAULT, T_STRING], $testContent);
$tokenArray = $tokens[$token];
$this->assertSame(T_STRING, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (code)');
$this->assertSame('T_STRING', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_STRING (type)');
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
}//end testNotDefaultKeyword()
/**
* Data provider.
*
* @see testNotDefaultKeyword()
*
* @return array
*/
public function dataNotDefaultKeyword()
{
return [
'class-constant-as-short-array-key' => ['/* testClassConstantAsShortArrayKey */'],
'class-property-as-short-array-key' => ['/* testClassPropertyAsShortArrayKey */'],
'namespaced-constant-as-short-array-key' => ['/* testNamespacedConstantAsShortArrayKey */'],
'fqn-global-constant-as-short-array-key' => ['/* testFQNGlobalConstantAsShortArrayKey */'],
'class-constant-as-long-array-key' => ['/* testClassConstantAsLongArrayKey */'],
'class-constant-as-yield-key' => ['/* testClassConstantAsYieldKey */'],
'class-constant-as-long-array-key-nested-in-match' => ['/* testClassConstantAsLongArrayKeyNestedInMatch */'],
'class-constant-as-long-array-key-nested-in-match-2' => ['/* testClassConstantAsLongArrayKeyNestedInMatchLevelDown */'],
'class-constant-as-short-array-key-nested-in-match' => ['/* testClassConstantAsShortArrayKeyNestedInMatch */'],
'class-constant-as-short-array-key-nested-in-match-2' => ['/* testClassConstantAsShortArrayKeyNestedInMatchLevelDown */'],
'class-constant-as-long-array-key-with-nested-match' => ['/* testClassConstantAsLongArrayKeyWithNestedMatch */'],
'class-constant-as-short-array-key-with-nested-match' => ['/* testClassConstantAsShortArrayKeyWithNestedMatch */'],
'class-constant-in-switch-case' => ['/* testClassConstantInSwitchCase */'],
'class-property-in-switch-case' => ['/* testClassPropertyInSwitchCase */'],
'namespaced-constant-in-switch-case' => ['/* testNamespacedConstantInSwitchCase */'],
'namespace-relative-constant-in-switch-case' => ['/* testNamespaceRelativeConstantInSwitchCase */'],
'class-constant-declaration' => ['/* testClassConstant */'],
'class-method-declaration' => [
'/* testMethodDeclaration */',
'default',
],
];
}//end dataNotDefaultKeyword()
/**
* Test a specific edge case where a scope opener would be incorrectly set.
*
* @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3326
*
* @return void
*/
public function testIssue3326()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testClassConstant */', [T_SEMICOLON]);
$tokenArray = $tokens[$token];
$this->assertArrayNotHasKey('scope_condition', $tokenArray, 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokenArray, 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokenArray, 'Scope closer is set');
}//end testIssue3326()
}//end class
@@ -0,0 +1,281 @@
<?php
function simpleLongArray($x) {
return array(
/* testLongArrayArrowSimple */
0 => 'Zero',
);
}
function simpleShortArray($x) {
return [
/* testShortArrayArrowSimple */
0 => 'Zero',
];
}
function simpleLongList($x) {
list(
/* testLongListArrowSimple */
0 => $a,
) = $x;
}
function simpleShortList($x) {
[
/* testShortListArrowSimple */
0 => $a,
] = $x;
}
function simpleYield($x) {
$i = 0;
foreach (explode("\n", $x) as $line) {
/* testYieldArrowSimple */
yield ++$i => $line;
}
}
function simpleForeach($x) {
/* testForeachArrowSimple */
foreach ($x as $k => $value) {}
}
function simpleMatch($x) {
return match ($x) {
/* testMatchArrowSimpleSingleCase */
0 => 'Zero',
/* testMatchArrowSimpleMultiCase */
2, 4, 6 => 'Zero',
/* testMatchArrowSimpleSingleCaseWithTrailingComma */
1, => 'Zero',
/* testMatchArrowSimpleMultiCaseWithTrailingComma */
3, 5, => 'Zero',
};
}
function simpleArrowFunction($y) {
/* testFnArrowSimple */
return fn ($y) => callMe($y);
}
function matchNestedInMatch() {
$x = match ($y) {
/* testMatchArrowNestedMatchOuter */
default, => match ($z) {
/* testMatchArrowNestedMatchInner */
1 => 1
},
};
}
function matchNestedInLongArrayValue() {
$array = array(
/* testLongArrayArrowWithNestedMatchValue1 */
'a' => match ($test) {
/* testMatchArrowInLongArrayValue1 */
1 => 'a',
/* testMatchArrowInLongArrayValue2 */
2 => 'b'
},
/* testLongArrayArrowWithNestedMatchValue2 */
$i => match ($test) {
/* testMatchArrowInLongArrayValue3 */
1 => 'a',
},
);
}
function matchNestedInShortArrayValue() {
$array = [
/* testShortArrayArrowWithNestedMatchValue1 */
'a' => match ($test) {
/* testMatchArrowInShortArrayValue1 */
1 => 'a',
/* testMatchArrowInShortArrayValue2 */
2 => 'b'
},
/* testShortArrayArrowWithNestedMatchValue2 */
$i => match ($test) {
/* testMatchArrowInShortArrayValue3 */
1 => 'a',
},
];
}
function matchNestedInLongArrayKey() {
$array = array(
match ($test) { /* testMatchArrowInLongArrayKey1 */ 1 => 'a', /* testMatchArrowInLongArrayKey2 */ 2 => 'b' }
/* testLongArrayArrowWithMatchKey */
=> 'dynamic keys, woho!',
);
}
function matchNestedInShortArrayKey() {
$array = [
match ($test) { /* testMatchArrowInShortArrayKey1 */ 1 => 'a', /* testMatchArrowInShortArrayKey2 */ 2 => 'b' }
/* testShortArrayArrowWithMatchKey */
=> 'dynamic keys, woho!',
];
}
function arraysNestedInMatch() {
$matcher = match ($x) {
/* testMatchArrowWithLongArrayBodyWithKeys */
0 => array(
/* testLongArrayArrowInMatchBody1 */
0 => 1,
/* testLongArrayArrowInMatchBody2 */
'a' => 2,
/* testLongArrayArrowInMatchBody3 */
'b' => 3
),
/* testMatchArrowWithShortArrayBodyWithoutKeys */
1 => [1, 2, 3],
/* testMatchArrowWithLongArrayBodyWithoutKeys */
2 => array( 1, [1, 2, 3], 2, 3),
/* testMatchArrowWithShortArrayBodyWithKeys */
3 => [
/* testShortArrayArrowInMatchBody1 */
0 => 1,
/* testShortArrayArrowInMatchBody2 */
'a' => array(1, 2, 3),
/* testShortArrayArrowInMatchBody3 */
'b' => 2,
3
],
/* testShortArrayArrowinMatchCase1 */
[4 => 'a', /* testShortArrayArrowinMatchCase2 */ 5 => 6]
/* testMatchArrowWithShortArrayWithKeysAsCase */
=> 'match with array as case value',
/* testShortArrayArrowinMatchCase3 */
[4 => 'a'], /* testLongArrayArrowinMatchCase4 */ array(5 => 6),
/* testMatchArrowWithMultipleArraysWithKeysAsCase */
=> 'match with multiple arrays as case value',
};
}
function matchNestedInArrowFunction($x) {
/* testFnArrowWithMatchInValue */
$fn = fn($x) => match(true) {
/* testMatchArrowInFnBody1 */
1, 2, 3, 4, 5 => 'foo',
/* testMatchArrowInFnBody2 */
default => 'bar',
};
}
function arrowFunctionsNestedInMatch($x) {
return match ($x) {
/* testMatchArrowWithFnBody1 */
1 => /* testFnArrowInMatchBody1 */ fn($y) => callMe($y),
/* testMatchArrowWithFnBody2 */
default => /* testFnArrowInMatchBody2 */ fn($y) => callThem($y)
};
}
function matchShortArrayMismash() {
$array = [
match ($test) {
/* testMatchArrowInComplexShortArrayKey1 */
1 => [ /* testShortArrayArrowInComplexMatchValueinShortArrayKey */ 1 => 'a'],
/* testMatchArrowInComplexShortArrayKey2 */
2 => 'b'
/* testShortArrayArrowInComplexMatchArrayMismash */
} => match ($test) {
/* testMatchArrowInComplexShortArrayValue1 */
1 => [ /* testShortArrayArrowInComplexMatchValueinShortArrayValue */ 1 => 'a'],
/* testMatchArrowInComplexShortArrayValue2 */
2 => /* testFnArrowInComplexMatchValueInShortArrayValue */ fn($y) => callMe($y)
},
];
}
function longListInMatch($x, $y) {
return match($x) {
/* testMatchArrowWithLongListBody */
1 => list('a' => $a, /* testLongListArrowInMatchBody */ 'b' => $b, 'c' => list('d' => $c)) = $y,
/* testLongListArrowInMatchCase */
list('a' => $a, 'b' => $b) = $y /* testMatchArrowWithLongListInCase */ => 'something'
};
}
function shortListInMatch($x, $y) {
return match($x) {
/* testMatchArrowWithShortListBody */
1 => ['a' => $a, 'b' => $b, 'c' => /* testShortListArrowInMatchBody */ ['d' => $c]] = $y,
/* testShortListArrowInMatchCase */
['a' => $a, 'b' => $b] = $y /* testMatchArrowWithShortListInCase */ => 'something'
};
}
function matchInLongList() {
/* testMatchArrowInLongListKey */
list(match($x) {1 => 1, 2 => 2} /* testLongListArrowWithMatchInKey */ => $a) = $array;
}
function matchInShortList() {
/* testMatchArrowInShortListKey */
[match($x) {1 => 1, 2 => 2} /* testShortListArrowWithMatchInKey */ => $a] = $array;
}
function longArrayWithConstantKey() {
$arr = array(
/* testLongArrayArrowWithClassConstantKey */
SomeClass::DEFAULT => 1,
);
}
function shortArrayWithConstantKey() {
$arr = [
/* testShortArrayArrowWithClassConstantKey */
SomeClass::DEFAULT => 1,
];
}
function yieldWithConstantKey() {
/* testYieldArrowWithClassConstantKey */
yield SomeClass::DEFAULT => 1;
}
function longArrayWithConstantKeyNestedInMatch() {
return match($x) {
/* testMatchArrowWithNestedLongArrayWithClassConstantKey */
default => array(
/* testLongArrayArrowWithClassConstantKeyNestedInMatch */
SomeClass::DEFAULT => 1,
),
};
}
function shortArrayWithConstantKeyNestedInMatch() {
return match($x) {
/* testMatchArrowWithNestedShortArrayWithClassConstantKey */
default => [
/* testShortArrayArrowWithClassConstantKeyNestedInMatch */
SomeClass::DEFAULT => 1,
],
};
}
function longArrayWithConstantKeyWithNestedMatch() {
return array(
/* testLongArrayArrowWithClassConstantKeyWithNestedMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchArrowNestedInLongArrayWithClassConstantKey */
default => 'foo'
},
);
}
function shortArrayWithConstantKeyWithNestedMatch() {
return [
/* testShortArrayArrowWithClassConstantKeyWithNestedMatch */
SomeClass::DEFAULT => match($x) {
/* testMatchArrowNestedInShortArrayWithClassConstantKey */
default => 'foo'
},
];
}
@@ -0,0 +1,237 @@
<?php
/**
* Tests the retokenization of the double arrow to T_MATCH_ARROW for PHP 8.0 match structures
* and makes sure that the tokenization of other double arrows (array, arrow function, yield)
* is not aversely affected.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2020-2021 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 DoubleArrowTest extends AbstractMethodUnitTest
{
/**
* Test that "normal" double arrows are correctly tokenized as `T_DOUBLE_ARROW`.
*
* @param string $testMarker The comment prefacing the target token.
*
* @dataProvider dataDoubleArrow
* @coversNothing
*
* @return void
*/
public function testDoubleArrow($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]);
$tokenArray = $tokens[$token];
$this->assertSame(T_DOUBLE_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_DOUBLE_ARROW (code)');
$this->assertSame('T_DOUBLE_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_DOUBLE_ARROW (type)');
}//end testDoubleArrow()
/**
* Data provider.
*
* @see testDoubleArrow()
*
* @return array
*/
public function dataDoubleArrow()
{
return [
'simple_long_array' => ['/* testLongArrayArrowSimple */'],
'simple_short_array' => ['/* testShortArrayArrowSimple */'],
'simple_long_list' => ['/* testLongListArrowSimple */'],
'simple_short_list' => ['/* testShortListArrowSimple */'],
'simple_yield' => ['/* testYieldArrowSimple */'],
'simple_foreach' => ['/* testForeachArrowSimple */'],
'long_array_with_match_value_1' => ['/* testLongArrayArrowWithNestedMatchValue1 */'],
'long_array_with_match_value_2' => ['/* testLongArrayArrowWithNestedMatchValue2 */'],
'short_array_with_match_value_1' => ['/* testShortArrayArrowWithNestedMatchValue1 */'],
'short_array_with_match_value_2' => ['/* testShortArrayArrowWithNestedMatchValue2 */'],
'long_array_with_match_key' => ['/* testLongArrayArrowWithMatchKey */'],
'short_array_with_match_key' => ['/* testShortArrayArrowWithMatchKey */'],
'long_array_in_match_body_1' => ['/* testLongArrayArrowInMatchBody1 */'],
'long_array_in_match_body_2' => ['/* testLongArrayArrowInMatchBody2 */'],
'long_array_in_match_body_3' => ['/* testLongArrayArrowInMatchBody3 */'],
'short_array_in_match_body_1' => ['/* testShortArrayArrowInMatchBody1 */'],
'short_array_in_match_body_2' => ['/* testShortArrayArrowInMatchBody2 */'],
'short_array_in_match_body_3' => ['/* testShortArrayArrowInMatchBody3 */'],
'short_array_in_match_case_1' => ['/* testShortArrayArrowinMatchCase1 */'],
'short_array_in_match_case_2' => ['/* testShortArrayArrowinMatchCase2 */'],
'short_array_in_match_case_3' => ['/* testShortArrayArrowinMatchCase3 */'],
'long_array_in_match_case_4' => ['/* testLongArrayArrowinMatchCase4 */'],
'in_complex_short_array_key_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayKey */'],
'in_complex_short_array_toplevel' => ['/* testShortArrayArrowInComplexMatchArrayMismash */'],
'in_complex_short_array_value_match_value' => ['/* testShortArrayArrowInComplexMatchValueinShortArrayValue */'],
'long_list_in_match_body' => ['/* testLongListArrowInMatchBody */'],
'long_list_in_match_case' => ['/* testLongListArrowInMatchCase */'],
'short_list_in_match_body' => ['/* testShortListArrowInMatchBody */'],
'short_list_in_match_case' => ['/* testShortListArrowInMatchCase */'],
'long_list_with_match_in_key' => ['/* testLongListArrowWithMatchInKey */'],
'short_list_with_match_in_key' => ['/* testShortListArrowWithMatchInKey */'],
'long_array_with_constant_default_in_key' => ['/* testLongArrayArrowWithClassConstantKey */'],
'short_array_with_constant_default_in_key' => ['/* testShortArrayArrowWithClassConstantKey */'],
'yield_with_constant_default_in_key' => ['/* testYieldArrowWithClassConstantKey */'],
'long_array_with_default_in_key_in_match' => ['/* testLongArrayArrowWithClassConstantKeyNestedInMatch */'],
'short_array_with_default_in_key_in_match' => ['/* testShortArrayArrowWithClassConstantKeyNestedInMatch */'],
'long_array_with_default_in_key_with_match' => ['/* testLongArrayArrowWithClassConstantKeyWithNestedMatch */'],
'short_array_with_default_in_key_with_match' => ['/* testShortArrayArrowWithClassConstantKeyWithNestedMatch */'],
];
}//end dataDoubleArrow()
/**
* Test that double arrows in match expressions which are the demarkation between a case and the return value
* are correctly tokenized as `T_MATCH_ARROW`.
*
* @param string $testMarker The comment prefacing the target token.
*
* @dataProvider dataMatchArrow
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testMatchArrow($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]);
$tokenArray = $tokens[$token];
$this->assertSame(T_MATCH_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_ARROW (code)');
$this->assertSame('T_MATCH_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_MATCH_ARROW (type)');
}//end testMatchArrow()
/**
* Data provider.
*
* @see testMatchArrow()
*
* @return array
*/
public function dataMatchArrow()
{
return [
'single_case' => ['/* testMatchArrowSimpleSingleCase */'],
'multi_case' => ['/* testMatchArrowSimpleMultiCase */'],
'single_case_with_trailing_comma' => ['/* testMatchArrowSimpleSingleCaseWithTrailingComma */'],
'multi_case_with_trailing_comma' => ['/* testMatchArrowSimpleMultiCaseWithTrailingComma */'],
'match_nested_outer' => ['/* testMatchArrowNestedMatchOuter */'],
'match_nested_inner' => ['/* testMatchArrowNestedMatchInner */'],
'in_long_array_value_1' => ['/* testMatchArrowInLongArrayValue1 */'],
'in_long_array_value_2' => ['/* testMatchArrowInLongArrayValue2 */'],
'in_long_array_value_3' => ['/* testMatchArrowInLongArrayValue3 */'],
'in_short_array_value_1' => ['/* testMatchArrowInShortArrayValue1 */'],
'in_short_array_value_2' => ['/* testMatchArrowInShortArrayValue2 */'],
'in_short_array_value_3' => ['/* testMatchArrowInShortArrayValue3 */'],
'in_long_array_key_1' => ['/* testMatchArrowInLongArrayKey1 */'],
'in_long_array_key_2' => ['/* testMatchArrowInLongArrayKey2 */'],
'in_short_array_key_1' => ['/* testMatchArrowInShortArrayKey1 */'],
'in_short_array_key_2' => ['/* testMatchArrowInShortArrayKey2 */'],
'with_long_array_value_with_keys' => ['/* testMatchArrowWithLongArrayBodyWithKeys */'],
'with_short_array_value_without_keys' => ['/* testMatchArrowWithShortArrayBodyWithoutKeys */'],
'with_long_array_value_without_keys' => ['/* testMatchArrowWithLongArrayBodyWithoutKeys */'],
'with_short_array_value_with_keys' => ['/* testMatchArrowWithShortArrayBodyWithKeys */'],
'with_short_array_with_keys_as_case' => ['/* testMatchArrowWithShortArrayWithKeysAsCase */'],
'with_multiple_arrays_with_keys_as_case' => ['/* testMatchArrowWithMultipleArraysWithKeysAsCase */'],
'in_fn_body_case' => ['/* testMatchArrowInFnBody1 */'],
'in_fn_body_default' => ['/* testMatchArrowInFnBody2 */'],
'with_fn_body_case' => ['/* testMatchArrowWithFnBody1 */'],
'with_fn_body_default' => ['/* testMatchArrowWithFnBody2 */'],
'in_complex_short_array_key_1' => ['/* testMatchArrowInComplexShortArrayKey1 */'],
'in_complex_short_array_key_2' => ['/* testMatchArrowInComplexShortArrayKey2 */'],
'in_complex_short_array_value_1' => ['/* testMatchArrowInComplexShortArrayValue1 */'],
'in_complex_short_array_value_2' => ['/* testMatchArrowInComplexShortArrayValue2 */'],
'with_long_list_in_body' => ['/* testMatchArrowWithLongListBody */'],
'with_long_list_in_case' => ['/* testMatchArrowWithLongListInCase */'],
'with_short_list_in_body' => ['/* testMatchArrowWithShortListBody */'],
'with_short_list_in_case' => ['/* testMatchArrowWithShortListInCase */'],
'in_long_list_key' => ['/* testMatchArrowInLongListKey */'],
'in_short_list_key' => ['/* testMatchArrowInShortListKey */'],
'with_long_array_value_with_default_key' => ['/* testMatchArrowWithNestedLongArrayWithClassConstantKey */'],
'with_short_array_value_with_default_key' => ['/* testMatchArrowWithNestedShortArrayWithClassConstantKey */'],
'in_long_array_value_with_default_key' => ['/* testMatchArrowNestedInLongArrayWithClassConstantKey */'],
'in_short_array_value_with_default_key' => ['/* testMatchArrowNestedInShortArrayWithClassConstantKey */'],
];
}//end dataMatchArrow()
/**
* Test that double arrows used as the scope opener for an arrow function
* are correctly tokenized as `T_FN_ARROW`.
*
* @param string $testMarker The comment prefacing the target token.
*
* @dataProvider dataFnArrow
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFnArrow($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken($testMarker, [T_DOUBLE_ARROW, T_MATCH_ARROW, T_FN_ARROW]);
$tokenArray = $tokens[$token];
$this->assertSame(T_FN_ARROW, $tokenArray['code'], 'Token tokenized as '.$tokenArray['type'].', not T_FN_ARROW (code)');
$this->assertSame('T_FN_ARROW', $tokenArray['type'], 'Token tokenized as '.$tokenArray['type'].', not T_FN_ARROW (type)');
}//end testFnArrow()
/**
* Data provider.
*
* @see testFnArrow()
*
* @return array
*/
public function dataFnArrow()
{
return [
'simple_fn' => ['/* testFnArrowSimple */'],
'with_match_as_value' => ['/* testFnArrowWithMatchInValue */'],
'in_match_value_case' => ['/* testFnArrowInMatchBody1 */'],
'in_match_value_default' => ['/* testFnArrowInMatchBody2 */'],
'in_complex_match_value_in_short_array' => ['/* testFnArrowInComplexMatchValueInShortArrayValue */'],
];
}//end dataFnArrow()
}//end class
@@ -0,0 +1,52 @@
<?php
// Test source: https://gist.github.com/iluuu1994/72e2154fc4150f2258316b0255b698f2#file-test-php
/* testSimple1 */
"$foo";
/* testSimple2 */
"{$foo}";
/* testSimple3 */
"${foo}";
/* testDIM1 */
"$foo[bar]";
/* testDIM2 */
"{$foo['bar']}";
/* testDIM3 */
"${foo['bar']}";
/* testProperty1 */
"$foo->bar";
/* testProperty2 */
"{$foo->bar}";
/* testMethod1 */
"{$foo->bar()}";
/* testClosure1 */
"{$foo()}";
/* testChain1 */
"{$foo['bar']->baz()()}";
/* testVariableVar1 */
"${$bar}";
/* testVariableVar2 */
"${(foo)}";
/* testVariableVar3 */
"${foo->bar}";
/* testNested1 */
"${foo["${bar}"]}";
/* testNested2 */
"${foo["${bar['baz']}"]}";
/* testNested3 */
"${foo->{$baz}}";
/* testNested4 */
"${foo->{${'a'}}}";
/* testNested5 */
"${foo->{"${'a'}"}}";
/* testParseError */
"${foo["${bar
@@ -0,0 +1,136 @@
<?php
/**
* Tests that embedded variables and expressions in double quoted strings are tokenized
* as one double quoted string token.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022 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 DoubleQuotedStringTest extends AbstractMethodUnitTest
{
/**
* Test that double quoted strings contain the complete string.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expectedContent The expected content of the double quoted string.
*
* @dataProvider dataDoubleQuotedString
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testDoubleQuotedString($testMarker, $expectedContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, T_DOUBLE_QUOTED_STRING);
$this->assertSame($expectedContent, $tokens[$target]['content']);
}//end testDoubleQuotedString()
/**
* Data provider.
*
* @see testDoubleQuotedString()
*
* @return array
*/
public function dataDoubleQuotedString()
{
return [
[
'testMarker' => '/* testSimple1 */',
'expectedContent' => '"$foo"',
],
[
'testMarker' => '/* testSimple2 */',
'expectedContent' => '"{$foo}"',
],
[
'testMarker' => '/* testSimple3 */',
'expectedContent' => '"${foo}"',
],
[
'testMarker' => '/* testDIM1 */',
'expectedContent' => '"$foo[bar]"',
],
[
'testMarker' => '/* testDIM2 */',
'expectedContent' => '"{$foo[\'bar\']}"',
],
[
'testMarker' => '/* testDIM3 */',
'expectedContent' => '"${foo[\'bar\']}"',
],
[
'testMarker' => '/* testProperty1 */',
'expectedContent' => '"$foo->bar"',
],
[
'testMarker' => '/* testProperty2 */',
'expectedContent' => '"{$foo->bar}"',
],
[
'testMarker' => '/* testMethod1 */',
'expectedContent' => '"{$foo->bar()}"',
],
[
'testMarker' => '/* testClosure1 */',
'expectedContent' => '"{$foo()}"',
],
[
'testMarker' => '/* testChain1 */',
'expectedContent' => '"{$foo[\'bar\']->baz()()}"',
],
[
'testMarker' => '/* testVariableVar1 */',
'expectedContent' => '"${$bar}"',
],
[
'testMarker' => '/* testVariableVar2 */',
'expectedContent' => '"${(foo)}"',
],
[
'testMarker' => '/* testVariableVar3 */',
'expectedContent' => '"${foo->bar}"',
],
[
'testMarker' => '/* testNested1 */',
'expectedContent' => '"${foo["${bar}"]}"',
],
[
'testMarker' => '/* testNested2 */',
'expectedContent' => '"${foo["${bar[\'baz\']}"]}"',
],
[
'testMarker' => '/* testNested3 */',
'expectedContent' => '"${foo->{$baz}}"',
],
[
'testMarker' => '/* testNested4 */',
'expectedContent' => '"${foo->{${\'a\'}}}"',
],
[
'testMarker' => '/* testNested5 */',
'expectedContent' => '"${foo->{"${\'a\'}"}}"',
],
[
'testMarker' => '/* testParseError */',
'expectedContent' => '"${foo["${bar
',
],
];
}//end dataDoubleQuotedString()
}//end class
@@ -0,0 +1,95 @@
<?php
enum Foo
{
/* testPureEnumCase */
case SOME_CASE;
}
enum Boo: int {
/* testBackingIntegerEnumCase */
case ONE = 1;
}
enum Hoo: string
{
/* testBackingStringEnumCase */
case ONE = 'one';
}
enum ComplexEnum: int implements SomeInterface
{
use SomeTrait {
traitMethod as enumMethod;
}
const SOME_CONSTANT = true;
/* testEnumCaseInComplexEnum */
case ONE = 1;
/* testEnumCaseIsCaseInsensitive */
CaSe TWO = 2;
public function someMethod(): bool
{
switch (true) {
/* testCaseWithSemicolonIsNotEnumCase */
case CONSTANT;
}
}
/* testEnumCaseAfterSwitch */
case THREE = 3;
public function someOtherMethod(): bool
{
switch (true):
case false:
endswitch;
}
/* testEnumCaseAfterSwitchWithEndSwitch */
case FOUR = 4;
}
switch (true) {
/* testCaseWithConstantIsNotEnumCase */
case CONSTANT:
/* testCaseWithConstantAndIdenticalIsNotEnumCase */
case CONSTANT === 1:
/* testCaseWithAssigmentToConstantIsNotEnumCase */
case CONSTANT = 1:
/* testIsNotEnumCaseIsCaseInsensitive */
cAsE CONSTANT:
}
switch ($x) {
/* testCaseInSwitchWhenCreatingEnumInSwitch1 */
case 'a': {
enum Foo {}
break;
}
/* testCaseInSwitchWhenCreatingEnumInSwitch2 */
case 'b';
enum Bar {}
break;
}
enum Foo: string {
/* testKeywordAsEnumCaseNameShouldBeString1 */
case INTERFACE = 'interface';
/* testKeywordAsEnumCaseNameShouldBeString2 */
case TRAIT = 'trait';
/* testKeywordAsEnumCaseNameShouldBeString3 */
case ENUM = 'enum';
/* testKeywordAsEnumCaseNameShouldBeString4 */
case FUNCTION = 'function';
/* testKeywordAsEnumCaseNameShouldBeString5 */
case FALSE = 'false';
/* testKeywordAsEnumCaseNameShouldBeString6 */
case DEFAULT = 'default';
/* testKeywordAsEnumCaseNameShouldBeString7 */
case ARRAY = 'array';
}
@@ -0,0 +1,157 @@
<?php
/**
* Tests converting enum "case" to T_ENUM_CASE.
*
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @copyright 2021 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 EnumCaseTest extends AbstractMethodUnitTest
{
/**
* Test that the enum "case" is converted to T_ENUM_CASE.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataEnumCases
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testEnumCases($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$enumCase = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
$this->assertSame(T_ENUM_CASE, $tokens[$enumCase]['code']);
$this->assertSame('T_ENUM_CASE', $tokens[$enumCase]['type']);
$this->assertArrayNotHasKey('scope_condition', $tokens[$enumCase], 'Scope condition is set');
$this->assertArrayNotHasKey('scope_opener', $tokens[$enumCase], 'Scope opener is set');
$this->assertArrayNotHasKey('scope_closer', $tokens[$enumCase], 'Scope closer is set');
}//end testEnumCases()
/**
* Data provider.
*
* @see testEnumCases()
*
* @return array
*/
public function dataEnumCases()
{
return [
['/* testPureEnumCase */'],
['/* testBackingIntegerEnumCase */'],
['/* testBackingStringEnumCase */'],
['/* testEnumCaseInComplexEnum */'],
['/* testEnumCaseIsCaseInsensitive */'],
['/* testEnumCaseAfterSwitch */'],
['/* testEnumCaseAfterSwitchWithEndSwitch */'],
];
}//end dataEnumCases()
/**
* Test that "case" that is not enum case is still tokenized as `T_CASE`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataNotEnumCases
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testNotEnumCases($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$case = $this->getTargetToken($testMarker, [T_ENUM_CASE, T_CASE]);
$this->assertSame(T_CASE, $tokens[$case]['code']);
$this->assertSame('T_CASE', $tokens[$case]['type']);
$this->assertArrayHasKey('scope_condition', $tokens[$case], 'Scope condition is not set');
$this->assertArrayHasKey('scope_opener', $tokens[$case], 'Scope opener is not set');
$this->assertArrayHasKey('scope_closer', $tokens[$case], 'Scope closer is not set');
}//end testNotEnumCases()
/**
* Data provider.
*
* @see testNotEnumCases()
*
* @return array
*/
public function dataNotEnumCases()
{
return [
['/* testCaseWithSemicolonIsNotEnumCase */'],
['/* testCaseWithConstantIsNotEnumCase */'],
['/* testCaseWithConstantAndIdenticalIsNotEnumCase */'],
['/* testCaseWithAssigmentToConstantIsNotEnumCase */'],
['/* testIsNotEnumCaseIsCaseInsensitive */'],
['/* testCaseInSwitchWhenCreatingEnumInSwitch1 */'],
['/* testCaseInSwitchWhenCreatingEnumInSwitch2 */'],
];
}//end dataNotEnumCases()
/**
* Test that "case" that is not enum case is still tokenized as `T_CASE`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataKeywordAsEnumCaseNameShouldBeString
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testKeywordAsEnumCaseNameShouldBeString($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$enumCaseName = $this->getTargetToken($testMarker, [T_STRING, T_INTERFACE, T_TRAIT, T_ENUM, T_FUNCTION, T_FALSE, T_DEFAULT, T_ARRAY]);
$this->assertSame(T_STRING, $tokens[$enumCaseName]['code']);
$this->assertSame('T_STRING', $tokens[$enumCaseName]['type']);
}//end testKeywordAsEnumCaseNameShouldBeString()
/**
* Data provider.
*
* @see testKeywordAsEnumCaseNameShouldBeString()
*
* @return array
*/
public function dataKeywordAsEnumCaseNameShouldBeString()
{
return [
['/* testKeywordAsEnumCaseNameShouldBeString1 */'],
['/* testKeywordAsEnumCaseNameShouldBeString2 */'],
['/* testKeywordAsEnumCaseNameShouldBeString3 */'],
['/* testKeywordAsEnumCaseNameShouldBeString4 */'],
];
}//end dataKeywordAsEnumCaseNameShouldBeString()
}//end class
@@ -0,0 +1,40 @@
<?php
try {
// Do something.
} catch(Exception $e) {
// Do something.
}
/* testTryCatchFinally */
finally {
// Do something.
}
/* testTryFinallyCatch */
try {
// Do something.
} finally {
// Do something.
} catch(Exception $e) {
// Do something.
}
/* testTryFinally */
try {
// Do something.
} FINALLY {
// Do something.
}
class FinallyAsMethod {
/* testFinallyUsedAsClassConstantName */
const FINALLY = 'foo';
/* testFinallyUsedAsMethodName */
public function finally() {
// Do something.
/* testFinallyUsedAsPropertyName */
$this->finally = 'foo';
}
}
@@ -0,0 +1,96 @@
<?php
/**
* Tests the tokenization of the finally keyword.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2021 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 FinallyTest extends AbstractMethodUnitTest
{
/**
* Test that the finally keyword is tokenized as such.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataFinallyKeyword
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testFinallyKeyword($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_FINALLY, T_STRING]);
$this->assertSame(T_FINALLY, $tokens[$target]['code']);
$this->assertSame('T_FINALLY', $tokens[$target]['type']);
}//end testFinallyKeyword()
/**
* Data provider.
*
* @see testFinallyKeyword()
*
* @return array
*/
public function dataFinallyKeyword()
{
return [
['/* testTryCatchFinally */'],
['/* testTryFinallyCatch */'],
['/* testTryFinally */'],
];
}//end dataFinallyKeyword()
/**
* Test that 'finally' when not used as the reserved keyword is tokenized as `T_STRING`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataFinallyNonKeyword
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testFinallyNonKeyword($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, [T_FINALLY, T_STRING]);
$this->assertSame(T_STRING, $tokens[$target]['code']);
$this->assertSame('T_STRING', $tokens[$target]['type']);
}//end testFinallyNonKeyword()
/**
* Data provider.
*
* @see testFinallyNonKeyword()
*
* @return array
*/
public function dataFinallyNonKeyword()
{
return [
['/* testFinallyUsedAsClassConstantName */'],
['/* testFinallyUsedAsMethodName */'],
['/* testFinallyUsedAsPropertyName */'],
];
}//end dataFinallyNonKeyword()
}//end class
@@ -0,0 +1,56 @@
<?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;
}
/* testNotGotoDeclarationEnumWithType */
enum Suit: string implements Colorful, CardGame {}
@@ -0,0 +1,175 @@
<?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',
],
[
'/* testNotGotoDeclarationEnumWithType */',
'Suit',
],
];
}//end dataNotAGotoDeclaration()
}//end class
@@ -0,0 +1,43 @@
<?php
/* testHeredocCloserNoIndent */
$heredoc = <<<EOD
some text
some text
some text
EOD;
/* testNowdocCloserNoIndent */
$nowdoc = <<<'EOD'
some text
some text
some text
EOD;
/* testHeredocCloserSpaceIndent */
$heredoc = <<<END
a
b
c
END;
/* testNowdocCloserSpaceIndent */
$nowdoc = <<<'END'
a
b
c
END;
/* testHeredocCloserTabIndent */
$heredoc = <<<"END"
a
b
c
END;
/* testNowdocCloserTabIndent */
$nowdoc = <<<'END'
a
b
c
END;
@@ -0,0 +1,150 @@
<?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\Config;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Files\DummyFile;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Heredoc/nowdoc closer token test.
*
* @requires PHP 7.3
*/
class HeredocNowdocCloserTest extends AbstractMethodUnitTest
{
/**
* Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file.
*
* {@internal This is a near duplicate of the original method. Only difference is that
* tab replacement is enabled for this test.}
*
* @return void
*/
public static function setUpBeforeClass()
{
$config = new Config();
$config->standards = ['PSR1'];
$config->tabWidth = 4;
$ruleset = new Ruleset($config);
// Default to a file with the same name as the test class. Extension is property based.
$relativeCN = str_replace(__NAMESPACE__, '', get_called_class());
$relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativeCN);
$pathToTestFile = realpath(__DIR__).$relativePath.'.'.static::$fileExtension;
// Make sure the file gets parsed correctly based on the file type.
$contents = 'phpcs_input_file: '.$pathToTestFile.PHP_EOL;
$contents .= file_get_contents($pathToTestFile);
self::$phpcsFile = new DummyFile($contents, $ruleset, $config);
self::$phpcsFile->process();
}//end setUpBeforeClass()
/**
* Verify that leading (indent) whitespace in a heredoc/nowdoc closer token get the tab replacement treatment.
*
* @param string $testMarker The comment prefacing the target token.
* @param array $expected Expectations for the token array.
*
* @dataProvider dataHeredocNowdocCloserTabReplacement
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap
*
* @return void
*/
public function testHeredocNowdocCloserTabReplacement($testMarker, $expected)
{
$tokens = self::$phpcsFile->getTokens();
$closer = $this->getTargetToken($testMarker, [T_END_HEREDOC, T_END_NOWDOC]);
foreach ($expected as $key => $value) {
if ($key === 'orig_content' && $value === null) {
$this->assertArrayNotHasKey($key, $tokens[$closer], "Unexpected 'orig_content' key found in the token array.");
continue;
}
$this->assertArrayHasKey($key, $tokens[$closer], "Key $key not found in the token array.");
$this->assertSame($value, $tokens[$closer][$key], "Value for key $key does not match expectation.");
}
}//end testHeredocNowdocCloserTabReplacement()
/**
* Data provider.
*
* @see testHeredocNowdocCloserTabReplacement()
*
* @return array
*/
public function dataHeredocNowdocCloserTabReplacement()
{
return [
[
'testMarker' => '/* testHeredocCloserNoIndent */',
'expected' => [
'length' => 3,
'content' => 'EOD',
'orig_content' => null,
],
],
[
'testMarker' => '/* testNowdocCloserNoIndent */',
'expected' => [
'length' => 3,
'content' => 'EOD',
'orig_content' => null,
],
],
[
'testMarker' => '/* testHeredocCloserSpaceIndent */',
'expected' => [
'length' => 7,
'content' => ' END',
'orig_content' => null,
],
],
[
'testMarker' => '/* testNowdocCloserSpaceIndent */',
'expected' => [
'length' => 8,
'content' => ' END',
'orig_content' => null,
],
],
[
'testMarker' => '/* testHeredocCloserTabIndent */',
'expected' => [
'length' => 8,
'content' => ' END',
'orig_content' => ' END',
],
],
[
'testMarker' => '/* testNowdocCloserTabIndent */',
'expected' => [
'length' => 7,
'content' => ' END',
'orig_content' => ' END',
],
],
];
}//end dataHeredocNowdocCloserTabReplacement()
}//end class
@@ -0,0 +1,193 @@
<?php
// Test source: https://gist.github.com/iluuu1994/72e2154fc4150f2258316b0255b698f2#file-test-php
/* testSimple1 */
$heredoc = <<<EOD
$foo
EOD;
/* testSimple2 */
$heredoc = <<<"EOD"
{$foo}
EOD;
/* testSimple3 */
$heredoc = <<<EOD
${foo}
EOD;
/* testDIM1 */
$heredoc = <<<"EOD"
$foo[bar]
EOD;
/* testDIM2 */
$heredoc = <<<EOD
{$foo['bar']}
EOD;
/* testDIM3 */
$heredoc = <<<"EOD"
${foo['bar']}
EOD;
/* testProperty1 */
$heredoc = <<<EOD
$foo->bar
EOD;
/* testProperty2 */
$heredoc = <<<"EOD"
{$foo->bar}
EOD;
/* testMethod1 */
$heredoc = <<<EOD
{$foo->bar()}
EOD;
/* testClosure1 */
$heredoc = <<<"EOD"
{$foo()}
EOD;
/* testChain1 */
$heredoc = <<<EOD
{$foo['bar']->baz()()}
EOD;
/* testVariableVar1 */
$heredoc = <<<"EOD"
${$bar}
EOD;
/* testVariableVar2 */
$heredoc = <<<EOD
${(foo)}
EOD;
/* testVariableVar3 */
$heredoc = <<<"EOD"
${foo->bar}
EOD;
/* testNested1 */
$heredoc = <<<EOD
${foo["${bar}"]}
EOD;
/* testNested2 */
$heredoc = <<<"EOD"
${foo["${bar['baz']}"]}
EOD;
/* testNested3 */
$heredoc = <<<EOD
${foo->{$baz}}
EOD;
/* testNested4 */
$heredoc = <<<"EOD"
${foo->{${'a'}}}
EOD;
/* testNested5 */
$heredoc = <<<EOD
${foo->{"${'a'}"}}
EOD;
/* testSimple1Wrapped */
$heredoc = <<<EOD
Do $foo Something
EOD;
/* testSimple2Wrapped */
$heredoc = <<<"EOD"
Do {$foo} Something
EOD;
/* testSimple3Wrapped */
$heredoc = <<<EOD
Do ${foo} Something
EOD;
/* testDIM1Wrapped */
$heredoc = <<<"EOD"
Do $foo[bar] Something
EOD;
/* testDIM2Wrapped */
$heredoc = <<<EOD
Do {$foo['bar']} Something
EOD;
/* testDIM3Wrapped */
$heredoc = <<<"EOD"
Do ${foo['bar']} Something
EOD;
/* testProperty1Wrapped */
$heredoc = <<<EOD
Do $foo->bar Something
EOD;
/* testProperty2Wrapped */
$heredoc = <<<"EOD"
Do {$foo->bar} Something
EOD;
/* testMethod1Wrapped */
$heredoc = <<<EOD
Do {$foo->bar()} Something
EOD;
/* testClosure1Wrapped */
$heredoc = <<<"EOD"
Do {$foo()} Something
EOD;
/* testChain1Wrapped */
$heredoc = <<<EOD
Do {$foo['bar']->baz()()} Something
EOD;
/* testVariableVar1Wrapped */
$heredoc = <<<"EOD"
Do ${$bar} Something
EOD;
/* testVariableVar2Wrapped */
$heredoc = <<<EOD
Do ${(foo)} Something
EOD;
/* testVariableVar3Wrapped */
$heredoc = <<<"EOD"
Do ${foo->bar} Something
EOD;
/* testNested1Wrapped */
$heredoc = <<<EOD
Do ${foo["${bar}"]} Something
EOD;
/* testNested2Wrapped */
$heredoc = <<<"EOD"
Do ${foo["${bar['baz']}"]} Something
EOD;
/* testNested3Wrapped */
$heredoc = <<<EOD
Do ${foo->{$baz}} Something
EOD;
/* testNested4Wrapped */
$heredoc = <<<"EOD"
Do ${foo->{${'a'}}} Something
EOD;
/* testNested5Wrapped */
$heredoc = <<<EOD
Do ${foo->{"${'a'}"}} Something
EOD;
@@ -0,0 +1,153 @@
<?php
/**
* Tests that embedded variables and expressions in heredoc strings are tokenized
* as one heredoc string token.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022 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 HeredocStringTest extends AbstractMethodUnitTest
{
/**
* Test that heredoc strings contain the complete interpolated string.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expectedContent The expected content of the heredoc string.
*
* @dataProvider dataHeredocString
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testHeredocString($testMarker, $expectedContent)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, T_HEREDOC);
$this->assertSame($expectedContent."\n", $tokens[$target]['content']);
}//end testHeredocString()
/**
* Test that heredoc strings contain the complete interpolated string when combined with other texts.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expectedContent The expected content of the heredoc string.
*
* @dataProvider dataHeredocString
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testHeredocStringWrapped($testMarker, $expectedContent)
{
$tokens = self::$phpcsFile->getTokens();
$testMarker = substr($testMarker, 0, -3).'Wrapped */';
$target = $this->getTargetToken($testMarker, T_HEREDOC);
$this->assertSame('Do '.$expectedContent." Something\n", $tokens[$target]['content']);
}//end testHeredocStringWrapped()
/**
* Data provider.
*
* @see testHeredocString()
*
* @return array
*/
public function dataHeredocString()
{
return [
[
'testMarker' => '/* testSimple1 */',
'expectedContent' => '$foo',
],
[
'testMarker' => '/* testSimple2 */',
'expectedContent' => '{$foo}',
],
[
'testMarker' => '/* testSimple3 */',
'expectedContent' => '${foo}',
],
[
'testMarker' => '/* testDIM1 */',
'expectedContent' => '$foo[bar]',
],
[
'testMarker' => '/* testDIM2 */',
'expectedContent' => '{$foo[\'bar\']}',
],
[
'testMarker' => '/* testDIM3 */',
'expectedContent' => '${foo[\'bar\']}',
],
[
'testMarker' => '/* testProperty1 */',
'expectedContent' => '$foo->bar',
],
[
'testMarker' => '/* testProperty2 */',
'expectedContent' => '{$foo->bar}',
],
[
'testMarker' => '/* testMethod1 */',
'expectedContent' => '{$foo->bar()}',
],
[
'testMarker' => '/* testClosure1 */',
'expectedContent' => '{$foo()}',
],
[
'testMarker' => '/* testChain1 */',
'expectedContent' => '{$foo[\'bar\']->baz()()}',
],
[
'testMarker' => '/* testVariableVar1 */',
'expectedContent' => '${$bar}',
],
[
'testMarker' => '/* testVariableVar2 */',
'expectedContent' => '${(foo)}',
],
[
'testMarker' => '/* testVariableVar3 */',
'expectedContent' => '${foo->bar}',
],
[
'testMarker' => '/* testNested1 */',
'expectedContent' => '${foo["${bar}"]}',
],
[
'testMarker' => '/* testNested2 */',
'expectedContent' => '${foo["${bar[\'baz\']}"]}',
],
[
'testMarker' => '/* testNested3 */',
'expectedContent' => '${foo->{$baz}}',
],
[
'testMarker' => '/* testNested4 */',
'expectedContent' => '${foo->{${\'a\'}}}',
],
[
'testMarker' => '/* testNested5 */',
'expectedContent' => '${foo->{"${\'a\'}"}}',
],
];
}//end dataHeredocString()
}//end class
@@ -0,0 +1,407 @@
<?php
/* testPositionalArgs */
array_fill(START_INDEX, \COUNT, MyNS\VALUE);
/* testNamedArgs */
array_fill(start_index: 0, count: 100, value: 50);
/* testNamedArgsMultiline */
array_fill(
start_index : 0,
count : 100,
value : 50,
);
/* testNamedArgsWithWhitespaceAndComments */
array_fill ( start_index : 0, /*comment */ count /*comment*/ : 100, value: 50);
/* testMixedPositionalAndNamedArgs */
htmlspecialchars($string, double_encode: false);
/* testNestedFunctionCallOuter */
array_fill(
start_index: $obj->getPos(skip: false),
count: count(array_or_countable: $array),
value: 50
);
array_fill(
start_index: /* testNestedFunctionCallInner1 */ $obj->getPos(skip: false),
count: /* testNestedFunctionCallInner2 */ count(array_or_countable: $array),
value: 50
);
/* testNamespaceOperatorFunction */
namespace\function_name(label:$string, more: false);
/* testNamespaceRelativeFunction */
Partially\Qualified\function_name(label:$string, more: false);
/* testNamespacedFQNFunction */
\Fully\Qualified\function_name(label: $string, more:false);
/* testVariableFunction */
$fn(label: $string, more:false);
/* testVariableVariableFunction */
${$fn}(label: $string, more:false);
/* testMethodCall */
$obj->methodName(label: $foo, more: $bar);
/* testVariableMethodCall */
$obj->{$var}(label: $foo, more: $bar);
/* testClassInstantiation */
$obj = new MyClass(label: $string, more:false);
/* testClassInstantiationSelf */
$obj = new self(label: $string, more:true);
/* testClassInstantiationStatic */
$obj = new static(label: $string, more:false);
/* testAnonClass */
$anon = new class(label: $string, more: false) {
public function __construct($label, $more) {}
};
function myfoo( $💩💩💩, $Пасха, $_valid) {}
/* testNonAsciiNames */
foo(💩💩💩: [], Пасха: 'text', _valid: 123);
/* testMixedPositionalAndNamedArgsWithTernary */
foo( $cond ? true : false, name: $value2 );
/* testNamedArgWithTernary */
foo( label: $cond ? true : false, more: $cond ? CONSTANT_A : CONSTANT_B );
/* testTernaryWithFunctionCallsInThenElse */
echo $cond ? foo( label: $something ) : foo( more: $something_else );
/* testTernaryWithConstantsInThenElse */
echo $cond ? CONSTANT_NAME : OTHER_CONSTANT;
switch ($s) {
/* testSwitchCaseWithConstant */
case MY_CONSTANT:
// Do something.
break;
/* testSwitchCaseWithClassProperty */
case $obj->property:
// Do something.
break;
/* testSwitchDefault */
default:
// Do something.
break;
}
/* testTernaryWithClosuresAndReturnTypes */
$closure = $cond ? function() : bool {return true;} : function() : int {return 123;};
/* testTernaryWithArrowFunctionsAndReturnTypes */
$fn = $cond ? fn() : bool => true : fn() : int => 123;
/* testCompileErrorNamedBeforePositional */
// Not the concern of PHPCS. Should still be handled.
test(param: $bar, $foo);
/* testDuplicateName1 */
// Error Exception, but not the concern of PHPCS. Should still be handled.
test(param: 1, /* testDuplicateName2 */ param: 2);
/* testIncorrectOrderWithVariadic */
// Error Exception, but not the concern of PHPCS. Should still be handled.
array_fill(start_index: 0, ...[100, 50]);
/* testCompileErrorIncorrectOrderWithVariadic */
// Not the concern of PHPCS. Should still be handled.
test(...$values, param: $value); // Compile-time error
/* testParseErrorNoValue */
// Not the concern of PHPCS. Should still be handled.
test(param1:, param2:);
/* testParseErrorDynamicName */
// Parse error. Ignore.
function_name($variableStoringParamName: $value);
/* testParseErrorExit */
// Exit is a language construct, not a function. Named params not supported, handle it anyway.
exit(status: $value);
/* testParseErrorEmpty */
// Empty is a language construct, not a function. Named params not supported, handle it anyway.
empty(variable: $value);
/* testParseErrorEval */
// Eval is a language construct, not a function. Named params not supported, handle it anyway.
eval(code: $value);
/* testParseErrorArbitraryParentheses */
// Parse error. Not named param, handle it anyway.
$calc = (something: $value / $other);
/* testReservedKeywordAbstract1 */
foobar(abstract: $value, /* testReservedKeywordAbstract2 */ abstract: $value);
/* testReservedKeywordAnd1 */
foobar(and: $value, /* testReservedKeywordAnd2 */ and: $value);
/* testReservedKeywordArray1 */
foobar(array: $value, /* testReservedKeywordArray2 */ array: $value);
/* testReservedKeywordAs1 */
foobar(as: $value, /* testReservedKeywordAs2 */ as: $value);
/* testReservedKeywordBreak1 */
foobar(break: $value, /* testReservedKeywordBreak2 */ break: $value);
/* testReservedKeywordCallable1 */
foobar(callable: $value, /* testReservedKeywordCallable2 */ callable: $value);
/* testReservedKeywordCase1 */
foobar(case: $value, /* testReservedKeywordCase2 */ case: $value);
/* testReservedKeywordCatch1 */
foobar(catch: $value, /* testReservedKeywordCatch2 */ catch: $value);
/* testReservedKeywordClass1 */
foobar(class: $value, /* testReservedKeywordClass2 */ class: $value);
/* testReservedKeywordClone1 */
foobar(clone: $value, /* testReservedKeywordClone2 */ clone: $value);
/* testReservedKeywordConst1 */
foobar(const: $value, /* testReservedKeywordConst2 */ const: $value);
/* testReservedKeywordContinue1 */
foobar(continue: $value, /* testReservedKeywordContinue2 */ continue: $value);
/* testReservedKeywordDeclare1 */
foobar(declare: $value, /* testReservedKeywordDeclare2 */ declare: $value);
/* testReservedKeywordDefault1 */
foobar(default: $value, /* testReservedKeywordDefault2 */ default: $value);
/* testReservedKeywordDie1 */
foobar(die: $value, /* testReservedKeywordDie2 */ die: $value);
/* testReservedKeywordDo1 */
foobar(do: $value, /* testReservedKeywordDo2 */ do: $value);
/* testReservedKeywordEcho1 */
foobar(echo: $value, /* testReservedKeywordEcho2 */ echo: $value);
/* testReservedKeywordElse1 */
foobar(else: $value, /* testReservedKeywordElse2 */ else: $value);
/* testReservedKeywordElseif1 */
foobar(elseif: $value, /* testReservedKeywordElseif2 */ elseif: $value);
/* testReservedKeywordEmpty1 */
foobar(empty: $value, /* testReservedKeywordEmpty2 */ empty: $value);
/* testReservedKeywordEnum1 */
foobar(enum: $value, /* testReservedKeywordEnum2 */ enum: $value);
/* testReservedKeywordEnddeclare1 */
foobar(enddeclare: $value, /* testReservedKeywordEnddeclare2 */ enddeclare: $value);
/* testReservedKeywordEndfor1 */
foobar(endfor: $value, /* testReservedKeywordEndfor2 */ endfor: $value);
/* testReservedKeywordEndforeach1 */
foobar(endforeach: $value, /* testReservedKeywordEndforeach2 */ endforeach: $value);
/* testReservedKeywordEndif1 */
foobar(endif: $value, /* testReservedKeywordEndif2 */ endif: $value);
/* testReservedKeywordEndswitch1 */
foobar(endswitch: $value, /* testReservedKeywordEndswitch2 */ endswitch: $value);
/* testReservedKeywordEndwhile1 */
foobar(endwhile: $value, /* testReservedKeywordEndwhile2 */ endwhile: $value);
/* testReservedKeywordEval1 */
foobar(eval: $value, /* testReservedKeywordEval2 */ eval: $value);
/* testReservedKeywordExit1 */
foobar(exit: $value, /* testReservedKeywordExit2 */ exit: $value);
/* testReservedKeywordExtends1 */
foobar(extends: $value, /* testReservedKeywordExtends2 */ extends: $value);
/* testReservedKeywordFinal1 */
foobar(final: $value, /* testReservedKeywordFinal2 */ final: $value);
/* testReservedKeywordFinally1 */
foobar(finally: $value, /* testReservedKeywordFinally2 */ finally: $value);
/* testReservedKeywordFn1 */
foobar(fn: $value, /* testReservedKeywordFn2 */ fn: $value);
/* testReservedKeywordFor1 */
foobar(for: $value, /* testReservedKeywordFor2 */ for: $value);
/* testReservedKeywordForeach1 */
foobar(foreach: $value, /* testReservedKeywordForeach2 */ foreach: $value);
/* testReservedKeywordFunction1 */
foobar(function: $value, /* testReservedKeywordFunction2 */ function: $value);
/* testReservedKeywordGlobal1 */
foobar(global: $value, /* testReservedKeywordGlobal2 */ global: $value);
/* testReservedKeywordGoto1 */
foobar(goto: $value, /* testReservedKeywordGoto2 */ goto: $value);
/* testReservedKeywordIf1 */
foobar(if: $value, /* testReservedKeywordIf2 */ if: $value);
/* testReservedKeywordImplements1 */
foobar(implements: $value, /* testReservedKeywordImplements2 */ implements: $value);
/* testReservedKeywordInclude1 */
foobar(include: $value, /* testReservedKeywordInclude2 */ include: $value);
/* testReservedKeywordInclude_once1 */
foobar(include_once: $value, /* testReservedKeywordInclude_once2 */ include_once: $value);
/* testReservedKeywordInstanceof1 */
foobar(instanceof: $value, /* testReservedKeywordInstanceof2 */ instanceof: $value);
/* testReservedKeywordInsteadof1 */
foobar(insteadof: $value, /* testReservedKeywordInsteadof2 */ insteadof: $value);
/* testReservedKeywordInterface1 */
foobar(interface: $value, /* testReservedKeywordInterface2 */ interface: $value);
/* testReservedKeywordIsset1 */
foobar(isset: $value, /* testReservedKeywordIsset2 */ isset: $value);
/* testReservedKeywordList1 */
foobar(list: $value, /* testReservedKeywordList2 */ list: $value);
/* testReservedKeywordMatch1 */
foobar(match: $value, /* testReservedKeywordMatch2 */ match: $value);
/* testReservedKeywordNamespace1 */
foobar(namespace: $value, /* testReservedKeywordNamespace2 */ namespace: $value);
/* testReservedKeywordNew1 */
foobar(new: $value, /* testReservedKeywordNew2 */ new: $value);
/* testReservedKeywordOr1 */
foobar(or: $value, /* testReservedKeywordOr2 */ or: $value);
/* testReservedKeywordPrint1 */
foobar(print: $value, /* testReservedKeywordPrint2 */ print: $value);
/* testReservedKeywordPrivate1 */
foobar(private: $value, /* testReservedKeywordPrivate2 */ private: $value);
/* testReservedKeywordProtected1 */
foobar(protected: $value, /* testReservedKeywordProtected2 */ protected: $value);
/* testReservedKeywordPublic1 */
foobar(public: $value, /* testReservedKeywordPublic2 */ public: $value);
/* testReservedKeywordReadonly1 */
foobar(readonly: $value, /* testReservedKeywordReadonly2 */ readonly: $value);
/* testReservedKeywordRequire1 */
foobar(require: $value, /* testReservedKeywordRequire2 */ require: $value);
/* testReservedKeywordRequire_once1 */
foobar(require_once: $value, /* testReservedKeywordRequire_once2 */ require_once: $value);
/* testReservedKeywordReturn1 */
foobar(return: $value, /* testReservedKeywordReturn2 */ return: $value);
/* testReservedKeywordStatic1 */
foobar(static: $value, /* testReservedKeywordStatic2 */ static: $value);
/* testReservedKeywordSwitch1 */
foobar(switch: $value, /* testReservedKeywordSwitch2 */ switch: $value);
/* testReservedKeywordThrow1 */
foobar(throw: $value, /* testReservedKeywordThrow2 */ throw: $value);
/* testReservedKeywordTrait1 */
foobar(trait: $value, /* testReservedKeywordTrait2 */ trait: $value);
/* testReservedKeywordTry1 */
foobar(try: $value, /* testReservedKeywordTry2 */ try: $value);
/* testReservedKeywordUnset1 */
foobar(unset: $value, /* testReservedKeywordUnset2 */ unset: $value);
/* testReservedKeywordUse1 */
foobar(use: $value, /* testReservedKeywordUse2 */ use: $value);
/* testReservedKeywordVar1 */
foobar(var: $value, /* testReservedKeywordVar2 */ var: $value);
/* testReservedKeywordWhile1 */
foobar(while: $value, /* testReservedKeywordWhile2 */ while: $value);
/* testReservedKeywordXor1 */
foobar(xor: $value, /* testReservedKeywordXor2 */ xor: $value);
/* testReservedKeywordYield1 */
foobar(yield: $value, /* testReservedKeywordYield2 */ yield: $value);
/* testReservedKeywordInt1 */
foobar(int: $value, /* testReservedKeywordInt2 */ int: $value);
/* testReservedKeywordFloat1 */
foobar(float: $value, /* testReservedKeywordFloat2 */ float: $value);
/* testReservedKeywordBool1 */
foobar(bool: $value, /* testReservedKeywordBool2 */ bool: $value);
/* testReservedKeywordString1 */
foobar(string: $value, /* testReservedKeywordString2 */ string: $value);
/* testReservedKeywordTrue1 */
foobar(true: $value, /* testReservedKeywordTrue2 */ true: $value);
/* testReservedKeywordFalse1 */
foobar(false: $value, /* testReservedKeywordFalse2 */ false: $value);
/* testReservedKeywordNull1 */
foobar(null: $value, /* testReservedKeywordNull2 */ null: $value);
/* testReservedKeywordVoid1 */
foobar(void: $value, /* testReservedKeywordVoid2 */ void: $value);
/* testReservedKeywordIterable1 */
foobar(iterable: $value, /* testReservedKeywordIterable2 */ iterable: $value);
/* testReservedKeywordObject1 */
foobar(object: $value, /* testReservedKeywordObject2 */ object: $value);
/* testReservedKeywordResource1 */
foobar(resource: $value, /* testReservedKeywordResource2 */ resource: $value);
/* testReservedKeywordMixed1 */
foobar(mixed: $value, /* testReservedKeywordMixed2 */ mixed: $value);
/* testReservedKeywordNumeric1 */
foobar(numeric: $value, /* testReservedKeywordNumeric2 */ numeric: $value);
/* testReservedKeywordParent1 */
foobar(parent: $value, /* testReservedKeywordParent2 */ parent: $value);
/* testReservedKeywordSelf1 */
foobar(self: $value, /* testReservedKeywordSelf2 */ self: $value);
/* testReservedKeywordNever1 */
foobar(never: $value, /* testReservedKeywordNever2 */ never: $value);
@@ -0,0 +1,885 @@
<?php
/**
* Tests the backfilling of the T_FN token to PHP < 7.4.
*
* @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;
use PHP_CodeSniffer\Util\Tokens;
class NamedFunctionCallArgumentsTest extends AbstractMethodUnitTest
{
/**
* Verify that parameter labels are tokenized as T_PARAM_NAME and that
* the colon after it is tokenized as a T_COLON.
*
* @param string $testMarker The comment prefacing the target token.
* @param array $parameters The token content for each parameter label to look for.
*
* @dataProvider dataNamedFunctionCallArguments
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNamedFunctionCallArguments($testMarker, $parameters)
{
$tokens = self::$phpcsFile->getTokens();
foreach ($parameters as $content) {
$label = $this->getTargetToken($testMarker, [T_STRING, T_PARAM_NAME], $content);
$this->assertSame(
T_PARAM_NAME,
$tokens[$label]['code'],
'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (code)'
);
$this->assertSame(
'T_PARAM_NAME',
$tokens[$label]['type'],
'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (type)'
);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end foreach
}//end testNamedFunctionCallArguments()
/**
* Data provider.
*
* @see testNamedFunctionCallArguments()
*
* @return array
*/
public function dataNamedFunctionCallArguments()
{
return [
[
'/* testNamedArgs */',
[
'start_index',
'count',
'value',
],
],
[
'/* testNamedArgsMultiline */',
[
'start_index',
'count',
'value',
],
],
[
'/* testNamedArgsWithWhitespaceAndComments */',
[
'start_index',
'count',
'value',
],
],
[
'/* testMixedPositionalAndNamedArgs */',
['double_encode'],
],
[
'/* testNestedFunctionCallOuter */',
[
'start_index',
'count',
'value',
],
],
[
'/* testNestedFunctionCallInner1 */',
['skip'],
],
[
'/* testNestedFunctionCallInner2 */',
['array_or_countable'],
],
[
'/* testNamespaceOperatorFunction */',
[
'label',
'more',
],
],
[
'/* testNamespaceRelativeFunction */',
[
'label',
'more',
],
],
[
'/* testNamespacedFQNFunction */',
[
'label',
'more',
],
],
[
'/* testVariableFunction */',
[
'label',
'more',
],
],
[
'/* testVariableVariableFunction */',
[
'label',
'more',
],
],
[
'/* testMethodCall */',
[
'label',
'more',
],
],
[
'/* testVariableMethodCall */',
[
'label',
'more',
],
],
[
'/* testClassInstantiation */',
[
'label',
'more',
],
],
[
'/* testClassInstantiationSelf */',
[
'label',
'more',
],
],
[
'/* testClassInstantiationStatic */',
[
'label',
'more',
],
],
[
'/* testAnonClass */',
[
'label',
'more',
],
],
[
'/* testNonAsciiNames */',
[
'💩💩💩',
'Пасха',
'_valid',
],
],
// Coding errors which should still be handled.
[
'/* testCompileErrorNamedBeforePositional */',
['param'],
],
[
'/* testDuplicateName1 */',
['param'],
],
[
'/* testDuplicateName2 */',
['param'],
],
[
'/* testIncorrectOrderWithVariadic */',
['start_index'],
],
[
'/* testCompileErrorIncorrectOrderWithVariadic */',
['param'],
],
[
'/* testParseErrorNoValue */',
[
'param1',
'param2',
],
],
[
'/* testParseErrorExit */',
['status'],
],
[
'/* testParseErrorEmpty */',
['variable'],
],
[
'/* testParseErrorEval */',
['code'],
],
[
'/* testParseErrorArbitraryParentheses */',
['something'],
],
];
}//end dataNamedFunctionCallArguments()
/**
* Verify that other T_STRING tokens within a function call are still tokenized as T_STRING.
*
* @param string $testMarker The comment prefacing the target token.
* @param string $content The token content to look for.
*
* @dataProvider dataOtherTstringInFunctionCall
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testOtherTstringInFunctionCall($testMarker, $content)
{
$tokens = self::$phpcsFile->getTokens();
$label = $this->getTargetToken($testMarker, [T_STRING, T_PARAM_NAME], $content);
$this->assertSame(
T_STRING,
$tokens[$label]['code'],
'Token tokenized as '.$tokens[$label]['type'].', not T_STRING (code)'
);
$this->assertSame(
'T_STRING',
$tokens[$label]['type'],
'Token tokenized as '.$tokens[$label]['type'].', not T_STRING (type)'
);
}//end testOtherTstringInFunctionCall()
/**
* Data provider.
*
* @see testOtherTstringInFunctionCall()
*
* @return array
*/
public function dataOtherTstringInFunctionCall()
{
return [
[
'/* testPositionalArgs */',
'START_INDEX',
],
[
'/* testPositionalArgs */',
'COUNT',
],
[
'/* testPositionalArgs */',
'VALUE',
],
[
'/* testNestedFunctionCallInner2 */',
'count',
],
];
}//end dataOtherTstringInFunctionCall()
/**
* Verify whether the colons are tokenized correctly when a ternary is used in a mixed
* positional and named arguments function call.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testMixedPositionalAndNamedArgsWithTernary()
{
$tokens = self::$phpcsFile->getTokens();
$true = $this->getTargetToken('/* testMixedPositionalAndNamedArgsWithTernary */', T_TRUE);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true);
$this->assertSame(
T_INLINE_ELSE,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)'
);
$this->assertSame(
'T_INLINE_ELSE',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)'
);
$label = $this->getTargetToken('/* testMixedPositionalAndNamedArgsWithTernary */', T_PARAM_NAME, 'name');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end testMixedPositionalAndNamedArgsWithTernary()
/**
* Verify whether the colons are tokenized correctly when a ternary is used
* in a named arguments function call.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNamedArgWithTernary()
{
$tokens = self::$phpcsFile->getTokens();
/*
* First argument.
*/
$label = $this->getTargetToken('/* testNamedArgWithTernary */', T_PARAM_NAME, 'label');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'First arg: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'First arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'First arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
$true = $this->getTargetToken('/* testNamedArgWithTernary */', T_TRUE);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true);
$this->assertSame(
T_INLINE_ELSE,
$tokens[$colon]['code'],
'First arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)'
);
$this->assertSame(
'T_INLINE_ELSE',
$tokens[$colon]['type'],
'First arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)'
);
/*
* Second argument.
*/
$label = $this->getTargetToken('/* testNamedArgWithTernary */', T_PARAM_NAME, 'more');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Second arg: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Second arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Second arg: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
$true = $this->getTargetToken('/* testNamedArgWithTernary */', T_STRING, 'CONSTANT_A');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($true + 1), null, true);
$this->assertSame(
T_INLINE_ELSE,
$tokens[$colon]['code'],
'Second arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)'
);
$this->assertSame(
'T_INLINE_ELSE',
$tokens[$colon]['type'],
'Second arg ternary: Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)'
);
}//end testNamedArgWithTernary()
/**
* Verify whether the colons are tokenized correctly when named arguments
* function calls are used in a ternary.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testTernaryWithFunctionCallsInThenElse()
{
$tokens = self::$phpcsFile->getTokens();
/*
* Then.
*/
$label = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_PARAM_NAME, 'label');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Function in then: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Function in then: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Function in then: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
$closeParens = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_CLOSE_PARENTHESIS);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($closeParens + 1), null, true);
$this->assertSame(
T_INLINE_ELSE,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)'
);
$this->assertSame(
'T_INLINE_ELSE',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)'
);
/*
* Else.
*/
$label = $this->getTargetToken('/* testTernaryWithFunctionCallsInThenElse */', T_PARAM_NAME, 'more');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Function in else: Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Function in else: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Function in else: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end testTernaryWithFunctionCallsInThenElse()
/**
* Verify whether the colons are tokenized correctly when constants are used in a ternary.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testTernaryWithConstantsInThenElse()
{
$tokens = self::$phpcsFile->getTokens();
$constant = $this->getTargetToken('/* testTernaryWithConstantsInThenElse */', T_STRING, 'CONSTANT_NAME');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($constant + 1), null, true);
$this->assertSame(
T_INLINE_ELSE,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (code)'
);
$this->assertSame(
'T_INLINE_ELSE',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_INLINE_ELSE (type)'
);
}//end testTernaryWithConstantsInThenElse()
/**
* Verify whether the colons are tokenized correctly in a switch statement.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testSwitchStatement()
{
$tokens = self::$phpcsFile->getTokens();
$label = $this->getTargetToken('/* testSwitchCaseWithConstant */', T_STRING, 'MY_CONSTANT');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'First case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'First case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
$label = $this->getTargetToken('/* testSwitchCaseWithClassProperty */', T_STRING, 'property');
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Second case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Second case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
$default = $this->getTargetToken('/* testSwitchDefault */', T_DEFAULT);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($default + 1), null, true);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Default case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Default case: Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end testSwitchStatement()
/**
* Verify that a variable parameter label (parse error) is still tokenized as T_VARIABLE.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testParseErrorVariableLabel()
{
$tokens = self::$phpcsFile->getTokens();
$label = $this->getTargetToken('/* testParseErrorDynamicName */', [T_VARIABLE, T_PARAM_NAME], '$variableStoringParamName');
$this->assertSame(
T_VARIABLE,
$tokens[$label]['code'],
'Token tokenized as '.$tokens[$label]['type'].', not T_VARIABLE (code)'
);
$this->assertSame(
'T_VARIABLE',
$tokens[$label]['type'],
'Token tokenized as '.$tokens[$label]['type'].', not T_VARIABLE (type)'
);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end testParseErrorVariableLabel()
/**
* Verify that reserved keywords used as a parameter label are tokenized as T_PARAM_NAME
* and that the colon after it is tokenized as a T_COLON.
*
* @param string $testMarker The comment prefacing the target token.
* @param array $tokenTypes The token codes to look for.
* @param string $tokenContent The token content to look for.
*
* @dataProvider dataReservedKeywordsAsName
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testReservedKeywordsAsName($testMarker, $tokenTypes, $tokenContent)
{
$tokens = self::$phpcsFile->getTokens();
$label = $this->getTargetToken($testMarker, $tokenTypes, $tokenContent);
$this->assertSame(
T_PARAM_NAME,
$tokens[$label]['code'],
'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (code)'
);
$this->assertSame(
'T_PARAM_NAME',
$tokens[$label]['type'],
'Token tokenized as '.$tokens[$label]['type'].', not T_PARAM_NAME (type)'
);
// Get the next non-empty token.
$colon = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($label + 1), null, true);
$this->assertSame(
':',
$tokens[$colon]['content'],
'Next token after parameter name is not a colon. Found: '.$tokens[$colon]['content']
);
$this->assertSame(
T_COLON,
$tokens[$colon]['code'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (code)'
);
$this->assertSame(
'T_COLON',
$tokens[$colon]['type'],
'Token tokenized as '.$tokens[$colon]['type'].', not T_COLON (type)'
);
}//end testReservedKeywordsAsName()
/**
* Data provider.
*
* @see testReservedKeywordsAsName()
*
* @return array
*/
public function dataReservedKeywordsAsName()
{
$reservedKeywords = [
// '__halt_compiler', NOT TESTABLE
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'enum',
'eval',
'exit',
'extends',
'final',
'finally',
'fn',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'match',
'namespace',
'new',
'or',
'print',
'private',
'protected',
'public',
'readonly',
'require',
'require_once',
'return',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'yield',
'int',
'float',
'bool',
'string',
'true',
'false',
'null',
'void',
'iterable',
'object',
'resource',
'mixed',
'numeric',
'never',
// Not reserved keyword, but do have their own token in PHPCS.
'parent',
'self',
];
$data = [];
foreach ($reservedKeywords as $keyword) {
$tokensTypes = [
T_PARAM_NAME,
T_STRING,
T_GOTO_LABEL,
];
$tokenName = 'T_'.strtoupper($keyword);
if ($keyword === 'and') {
$tokensTypes[] = T_LOGICAL_AND;
} else if ($keyword === 'die') {
$tokensTypes[] = T_EXIT;
} else if ($keyword === 'or') {
$tokensTypes[] = T_LOGICAL_OR;
} else if ($keyword === 'xor') {
$tokensTypes[] = T_LOGICAL_XOR;
} else if ($keyword === '__halt_compiler') {
$tokensTypes[] = T_HALT_COMPILER;
} else if (defined($tokenName) === true) {
$tokensTypes[] = constant($tokenName);
}
$data[$keyword.'FirstParam'] = [
'/* testReservedKeyword'.ucfirst($keyword).'1 */',
$tokensTypes,
$keyword,
];
$data[$keyword.'SecondParam'] = [
'/* testReservedKeyword'.ucfirst($keyword).'2 */',
$tokensTypes,
$keyword,
];
}//end foreach
return $data;
}//end dataReservedKeywordsAsName()
}//end class
@@ -0,0 +1,29 @@
<?php
/*
* Null safe operator.
*/
/* testObjectOperator */
echo $obj->foo;
/* testNullsafeObjectOperator */
echo $obj?->foo;
/* testNullsafeObjectOperatorWriteContext */
// Intentional parse error, but not the concern of the tokenizer.
$foo?->bar->baz = 'baz';
/* testTernaryThen */
echo $obj ? $obj->prop : $other->prop;
/* testParseErrorWhitespaceNotAllowed */
echo $obj ?
-> foo;
/* testParseErrorCommentNotAllowed */
echo $obj ?/*comment*/-> foo;
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
echo $obj?
@@ -0,0 +1,140 @@
<?php
/**
* Tests the backfill for the PHP >= 8.0 nullsafe object operator.
*
* @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;
use PHP_CodeSniffer\Util\Tokens;
class NullsafeObjectOperatorTest extends AbstractMethodUnitTest
{
/**
* Tokens to search for.
*
* @var array
*/
protected $find = [
T_NULLSAFE_OBJECT_OPERATOR,
T_OBJECT_OPERATOR,
T_INLINE_THEN,
];
/**
* Test that a normal object operator is still tokenized as such.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testObjectOperator()
{
$tokens = self::$phpcsFile->getTokens();
$operator = $this->getTargetToken('/* testObjectOperator */', $this->find);
$this->assertSame(T_OBJECT_OPERATOR, $tokens[$operator]['code'], 'Failed asserting code is object operator');
$this->assertSame('T_OBJECT_OPERATOR', $tokens[$operator]['type'], 'Failed asserting type is object operator');
}//end testObjectOperator()
/**
* Test that a nullsafe object operator is tokenized as such.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataNullsafeObjectOperator
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testNullsafeObjectOperator($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$operator = $this->getTargetToken($testMarker, $this->find);
$this->assertSame(T_NULLSAFE_OBJECT_OPERATOR, $tokens[$operator]['code'], 'Failed asserting code is nullsafe object operator');
$this->assertSame('T_NULLSAFE_OBJECT_OPERATOR', $tokens[$operator]['type'], 'Failed asserting type is nullsafe object operator');
}//end testNullsafeObjectOperator()
/**
* Data provider.
*
* @see testNullsafeObjectOperator()
*
* @return array
*/
public function dataNullsafeObjectOperator()
{
return [
['/* testNullsafeObjectOperator */'],
['/* testNullsafeObjectOperatorWriteContext */'],
];
}//end dataNullsafeObjectOperator()
/**
* Test that a question mark not followed by an object operator is tokenized as T_TERNARY_THEN.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param bool $testObjectOperator Whether to test for the next non-empty token being tokenized
* as an object operator.
*
* @dataProvider dataTernaryThen
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testTernaryThen($testMarker, $testObjectOperator=false)
{
$tokens = self::$phpcsFile->getTokens();
$operator = $this->getTargetToken($testMarker, $this->find);
$this->assertSame(T_INLINE_THEN, $tokens[$operator]['code'], 'Failed asserting code is inline then');
$this->assertSame('T_INLINE_THEN', $tokens[$operator]['type'], 'Failed asserting type is inline then');
if ($testObjectOperator === true) {
$next = self::$phpcsFile->findNext(Tokens::$emptyTokens, ($operator + 1), null, true);
$this->assertSame(T_OBJECT_OPERATOR, $tokens[$next]['code'], 'Failed asserting code is object operator');
$this->assertSame('T_OBJECT_OPERATOR', $tokens[$next]['type'], 'Failed asserting type is object operator');
}
}//end testTernaryThen()
/**
* Data provider.
*
* @see testTernaryThen()
*
* @return array
*/
public function dataTernaryThen()
{
return [
['/* testTernaryThen */'],
[
'/* testParseErrorWhitespaceNotAllowed */',
true,
],
[
'/* testParseErrorCommentNotAllowed */',
true,
],
['/* testLiveCoding */'],
];
}//end dataTernaryThen()
}//end class
@@ -0,0 +1,19 @@
<?php
/* testClassExtends */
class Foo extends namespace\Bar {}
/* testClassImplements */
$anon = new class implements namespace\Foo {}
/* testInterfaceExtends */
interface FooBar extends namespace\BarFoo {}
/* testFunctionReturnType */
function foo() : namespace\Baz {}
/* testClosureReturnType */
$closure = function () : namespace\Baz {};
/* testArrowFunctionReturnType */
$fn = fn() : namespace\Baz => new namespace\Baz;
@@ -0,0 +1,98 @@
<?php
/**
* Tests the adding of the "bracket_opener/closer" keys to use group tokens.
*
* @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 ScopeSettingWithNamespaceOperatorTest extends AbstractMethodUnitTest
{
/**
* Test that the scope opener/closers are set correctly when the namespace keyword is encountered as an operator.
*
* @param string $testMarker The comment which prefaces the target tokens in the test file.
* @param int|string[] $tokenTypes The token type to search for.
* @param int|string[] $open Optional. The token type for the scope opener.
* @param int|string[] $close Optional. The token type for the scope closer.
*
* @dataProvider dataScopeSetting
* @covers PHP_CodeSniffer\Tokenizers\Tokenizer::recurseScopeMap
*
* @return void
*/
public function testScopeSetting($testMarker, $tokenTypes, $open=T_OPEN_CURLY_BRACKET, $close=T_CLOSE_CURLY_BRACKET)
{
$tokens = self::$phpcsFile->getTokens();
$target = $this->getTargetToken($testMarker, $tokenTypes);
$opener = $this->getTargetToken($testMarker, $open);
$closer = $this->getTargetToken($testMarker, $close);
$this->assertArrayHasKey('scope_opener', $tokens[$target], 'Scope opener missing');
$this->assertArrayHasKey('scope_closer', $tokens[$target], 'Scope closer missing');
$this->assertSame($opener, $tokens[$target]['scope_opener'], 'Scope opener not same');
$this->assertSame($closer, $tokens[$target]['scope_closer'], 'Scope closer not same');
$this->assertArrayHasKey('scope_opener', $tokens[$opener], 'Scope opener missing for open curly');
$this->assertArrayHasKey('scope_closer', $tokens[$opener], 'Scope closer missing for open curly');
$this->assertSame($opener, $tokens[$opener]['scope_opener'], 'Scope opener not same for open curly');
$this->assertSame($closer, $tokens[$opener]['scope_closer'], 'Scope closer not same for open curly');
$this->assertArrayHasKey('scope_opener', $tokens[$closer], 'Scope opener missing for close curly');
$this->assertArrayHasKey('scope_closer', $tokens[$closer], 'Scope closer missing for close curly');
$this->assertSame($opener, $tokens[$closer]['scope_opener'], 'Scope opener not same for close curly');
$this->assertSame($closer, $tokens[$closer]['scope_closer'], 'Scope closer not same for close curly');
}//end testScopeSetting()
/**
* Data provider.
*
* @see testScopeSetting()
*
* @return array
*/
public function dataScopeSetting()
{
return [
[
'/* testClassExtends */',
[T_CLASS],
],
[
'/* testClassImplements */',
[T_ANON_CLASS],
],
[
'/* testInterfaceExtends */',
[T_INTERFACE],
],
[
'/* testFunctionReturnType */',
[T_FUNCTION],
],
[
'/* testClosureReturnType */',
[T_CLOSURE],
],
[
'/* testArrowFunctionReturnType */',
[T_FN],
[T_FN_ARROW],
[T_SEMICOLON],
],
];
}//end dataScopeSetting()
}//end class
@@ -0,0 +1,111 @@
<?php
/*
* Square brackets.
*/
/* testArrayAccess1 */
$var = $array[10];
$var = $array[++$y]/* testArrayAccess2 */[$x];
/* testArrayAssignment */
$array[] = $var;
/* testFunctionCallDereferencing */
$var = function_call()[$x];
/* testMethodCallDereferencing */
$var = $obj->function_call()[$x];
/* testStaticMethodCallDereferencing */
$var = ClassName::function_call()[$x];
/* testPropertyDereferencing */
$var = $obj->property[2];
/* testPropertyDereferencingWithInaccessibleName */
$var = $ref->{'ref-type'}[1];
/* testStaticPropertyDereferencing */
$var ClassName::$property[2];
/* testStringDereferencing */
$var = 'PHP'[1];
/* testStringDereferencingDoubleQuoted */
$var = "PHP"[$y];
/* testConstantDereferencing */
$var = MY_CONSTANT[1];
/* testClassConstantDereferencing */
$var ClassName::CONSTANT_NAME[2];
/* testMagicConstantDereferencing */
$var = __FILE__[0];
/* testArrayAccessCurlyBraces */
$var = $array{'key'}['key'];
/* testArrayLiteralDereferencing */
echo array(1, 2, 3)[0];
echo [1, 2, 3]/* testShortArrayLiteralDereferencing */[0];
/* testClassMemberDereferencingOnInstantiation1 */
(new foo)[0];
/* testClassMemberDereferencingOnInstantiation2 */
$a = (new Foo( array(1, array(4, 5), 3) ))[1][0];
/* testClassMemberDereferencingOnClone */
echo (clone $iterable)[20];
/* testNullsafeMethodCallDereferencing */
$var = $obj?->function_call()[$x];
/* testInterpolatedStringDereferencing */
$var = "PHP{$rocks}"[1];
/*
* Short array brackets.
*/
/* testShortArrayDeclarationEmpty */
$array = [];
/* testShortArrayDeclarationWithOneValue */
$array = [1];
/* testShortArrayDeclarationWithMultipleValues */
$array = [1, 2, 3];
/* testShortArrayDeclarationWithDereferencing */
echo [1, 2, 3][0];
/* testShortListDeclaration */
[ $a, $b ] = $array;
[ $a, $b, /* testNestedListDeclaration */, [$c, $d]] = $array;
/* testArrayWithinFunctionCall */
$var = functionCall([$x, $y]);
if ( true ) {
/* testShortListDeclarationAfterBracedControlStructure */
[ $a ] = [ 'hi' ];
}
if ( true )
/* testShortListDeclarationAfterNonBracedControlStructure */
[ $a ] = [ 'hi' ];
if ( true ) :
/* testShortListDeclarationAfterAlternativeControlStructure */
[ $a ] = [ 'hi' ];
endif;
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$array = [
@@ -0,0 +1,135 @@
<?php
/**
* Tests the conversion of square bracket tokens to short array tokens.
*
* @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 ShortArrayTest extends AbstractMethodUnitTest
{
/**
* Test that real square brackets are still tokenized as square brackets.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataSquareBrackets
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testSquareBrackets($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]);
$this->assertSame(T_OPEN_SQUARE_BRACKET, $tokens[$opener]['code']);
$this->assertSame('T_OPEN_SQUARE_BRACKET', $tokens[$opener]['type']);
if (isset($tokens[$opener]['bracket_closer']) === true) {
$closer = $tokens[$opener]['bracket_closer'];
$this->assertSame(T_CLOSE_SQUARE_BRACKET, $tokens[$closer]['code']);
$this->assertSame('T_CLOSE_SQUARE_BRACKET', $tokens[$closer]['type']);
}
}//end testSquareBrackets()
/**
* Data provider.
*
* @see testSquareBrackets()
*
* @return array
*/
public function dataSquareBrackets()
{
return [
'array access 1' => ['/* testArrayAccess1 */'],
'array access 2' => ['/* testArrayAccess2 */'],
'array assignment' => ['/* testArrayAssignment */'],
'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
'method call dereferencing' => ['/* testMethodCallDereferencing */'],
'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
'property dereferencing' => ['/* testPropertyDereferencing */'],
'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
'string dereferencing single quotes' => ['/* testStringDereferencing */'],
'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
'global constant dereferencing' => ['/* testConstantDereferencing */'],
'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
'live coding' => ['/* testLiveCoding */'],
];
}//end dataSquareBrackets()
/**
* Test that short arrays and short lists are still tokenized as short arrays.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataShortArrays
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testShortArrays($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_OPEN_SQUARE_BRACKET, T_OPEN_SHORT_ARRAY]);
$this->assertSame(T_OPEN_SHORT_ARRAY, $tokens[$opener]['code']);
$this->assertSame('T_OPEN_SHORT_ARRAY', $tokens[$opener]['type']);
if (isset($tokens[$opener]['bracket_closer']) === true) {
$closer = $tokens[$opener]['bracket_closer'];
$this->assertSame(T_CLOSE_SHORT_ARRAY, $tokens[$closer]['code']);
$this->assertSame('T_CLOSE_SHORT_ARRAY', $tokens[$closer]['type']);
}
}//end testShortArrays()
/**
* Data provider.
*
* @see testShortArrays()
*
* @return array
*/
public function dataShortArrays()
{
return [
'short array empty' => ['/* testShortArrayDeclarationEmpty */'],
'short array with value' => ['/* testShortArrayDeclarationWithOneValue */'],
'short array with values' => ['/* testShortArrayDeclarationWithMultipleValues */'],
'short array with dereferencing' => ['/* testShortArrayDeclarationWithDereferencing */'],
'short list' => ['/* testShortListDeclaration */'],
'short list nested' => ['/* testNestedListDeclaration */'],
'short array within function call' => ['/* testArrayWithinFunctionCall */'],
'short list after braced control structure' => ['/* testShortListDeclarationAfterBracedControlStructure */'],
'short list after non-braced control structure' => ['/* testShortListDeclarationAfterNonBracedControlStructure */'],
'short list after alternative control structure' => ['/* testShortListDeclarationAfterAlternativeControlStructure */'],
];
}//end dataShortArrays()
}//end class
@@ -0,0 +1,139 @@
<?php
/* testSingleLineSlashComment */
// Comment
/* testSingleLineSlashCommentTrailing */
echo 'a'; // Comment
/* testSingleLineSlashAnnotation */
// phpcs:disable Stnd.Cat
/* testMultiLineSlashComment */
// Comment1
// Comment2
// Comment3
/* testMultiLineSlashCommentWithIndent */
// Comment1
// Comment2
// Comment3
/* testMultiLineSlashCommentWithAnnotationStart */
// phpcs:ignore Stnd.Cat
// Comment2
// Comment3
/* testMultiLineSlashCommentWithAnnotationMiddle */
// Comment1
// @phpcs:ignore Stnd.Cat
// Comment3
/* testMultiLineSlashCommentWithAnnotationEnd */
// Comment1
// Comment2
// phpcs:ignore Stnd.Cat
/* testSingleLineStarComment */
/* Single line star comment */
/* testSingleLineStarCommentTrailing */
echo 'a'; /* Comment */
/* testSingleLineStarAnnotation */
/* phpcs:ignore Stnd.Cat */
/* testMultiLineStarComment */
/* Comment1
* Comment2
* Comment3 */
/* testMultiLineStarCommentWithIndent */
/* Comment1
* Comment2
* Comment3 */
/* testMultiLineStarCommentWithAnnotationStart */
/* @phpcs:ignore Stnd.Cat
* Comment2
* Comment3 */
/* testMultiLineStarCommentWithAnnotationMiddle */
/* Comment1
* phpcs:ignore Stnd.Cat
* Comment3 */
/* testMultiLineStarCommentWithAnnotationEnd */
/* Comment1
* Comment2
* phpcs:ignore Stnd.Cat */
/* testSingleLineDocblockComment */
/** Comment */
/* testSingleLineDocblockCommentTrailing */
$prop = 123; /** Comment */
/* testSingleLineDocblockAnnotation */
/** phpcs:ignore Stnd.Cat.Sniff */
/* testMultiLineDocblockComment */
/**
* Comment1
* Comment2
*
* @tag Comment
*/
/* testMultiLineDocblockCommentWithIndent */
/**
* Comment1
* Comment2
*
* @tag Comment
*/
/* testMultiLineDocblockCommentWithAnnotation */
/**
* Comment
*
* phpcs:ignore Stnd.Cat
* @tag Comment
*/
/* testMultiLineDocblockCommentWithTagAnnotation */
/**
* Comment
*
* @phpcs:ignore Stnd.Cat
* @tag Comment
*/
/* testSingleLineHashComment */
# Comment
/* testSingleLineHashCommentTrailing */
echo 'a'; # Comment
/* testMultiLineHashComment */
# Comment1
# Comment2
# Comment3
/* testMultiLineHashCommentWithIndent */
# Comment1
# Comment2
# Comment3
/* testSingleLineSlashCommentNoNewLineAtEnd */
// Slash ?>
<?php
/* testSingleLineHashCommentNoNewLineAtEnd */
# Hash ?>
<?php
/* testCommentAtEndOfFile */
/* Comment
@@ -0,0 +1,63 @@
<?php
/* testSingleLineSlashComment */
// Comment
/* testSingleLineSlashCommentTrailing */
echo 'a'; // Comment
/* testSingleLineSlashAnnotation */
// phpcs:disable Stnd.Cat
/* testMultiLineSlashComment */
// Comment1
// Comment2
// Comment3
/* testMultiLineSlashCommentWithIndent */
// Comment1
// Comment2
// Comment3
/* testMultiLineSlashCommentWithAnnotationStart */
// phpcs:ignore Stnd.Cat
// Comment2
// Comment3
/* testMultiLineSlashCommentWithAnnotationMiddle */
// Comment1
// @phpcs:ignore Stnd.Cat
// Comment3
/* testMultiLineSlashCommentWithAnnotationEnd */
// Comment1
// Comment2
// phpcs:ignore Stnd.Cat
/* testSingleLineSlashCommentNoNewLineAtEnd */
// Slash ?>
<?php
/* testSingleLineHashComment */
# Comment
/* testSingleLineHashCommentTrailing */
echo 'a'; # Comment
/* testMultiLineHashComment */
# Comment1
# Comment2
# Comment3
/* testMultiLineHashCommentWithIndent */
# Comment1
# Comment2
# Comment3
/* testSingleLineHashCommentNoNewLineAtEnd */
# Hash ?>
<?php
/* testCommentAtEndOfFile */
/* Comment
@@ -0,0 +1,367 @@
<?php
/**
* Tests the comment tokenization with Windows line endings.
*
* Basically the same as the StableCommentWhitespaceTest, but now for
* Windows line endings.
*
* @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;
use PHP_CodeSniffer\Util\Tokens;
class StableCommentWhitespaceWinTest extends AbstractMethodUnitTest
{
/**
* Test that comment tokenization with new lines at the end of the comment is stable.
*
* @param string $testMarker The comment prefacing the test.
* @param array $expectedTokens The tokenization expected.
*
* @dataProvider dataCommentTokenization
* @covers PHP_CodeSniffer\Tokenizers\PHP::tokenize
*
* @return void
*/
public function testCommentTokenization($testMarker, $expectedTokens)
{
$tokens = self::$phpcsFile->getTokens();
$comment = $this->getTargetToken($testMarker, Tokens::$commentTokens);
foreach ($expectedTokens as $key => $tokenInfo) {
$this->assertSame(constant($tokenInfo['type']), $tokens[$comment]['code']);
$this->assertSame($tokenInfo['type'], $tokens[$comment]['type']);
$this->assertSame($tokenInfo['content'], $tokens[$comment]['content']);
++$comment;
}
}//end testCommentTokenization()
/**
* Data provider.
*
* @see testCommentTokenization()
*
* @return array
*/
public function dataCommentTokenization()
{
return [
[
'/* testSingleLineSlashComment */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testSingleLineSlashCommentTrailing */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testSingleLineSlashAnnotation */',
[
[
'type' => 'T_PHPCS_DISABLE',
'content' => '// phpcs:disable Stnd.Cat
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineSlashComment */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment1
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment2
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineSlashCommentWithIndent */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment1
',
],
[
'type' => 'T_WHITESPACE',
'content' => ' ',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment2
',
],
[
'type' => 'T_WHITESPACE',
'content' => ' ',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineSlashCommentWithAnnotationStart */',
[
[
'type' => 'T_PHPCS_IGNORE',
'content' => '// phpcs:ignore Stnd.Cat
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment2
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineSlashCommentWithAnnotationMiddle */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment1
',
],
[
'type' => 'T_PHPCS_IGNORE',
'content' => '// @phpcs:ignore Stnd.Cat
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineSlashCommentWithAnnotationEnd */',
[
[
'type' => 'T_COMMENT',
'content' => '// Comment1
',
],
[
'type' => 'T_COMMENT',
'content' => '// Comment2
',
],
[
'type' => 'T_PHPCS_IGNORE',
'content' => '// phpcs:ignore Stnd.Cat
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testSingleLineSlashCommentNoNewLineAtEnd */',
[
[
'type' => 'T_COMMENT',
'content' => '// Slash ',
],
[
'type' => 'T_CLOSE_TAG',
'content' => '?>
',
],
],
],
[
'/* testSingleLineHashComment */',
[
[
'type' => 'T_COMMENT',
'content' => '# Comment
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testSingleLineHashCommentTrailing */',
[
[
'type' => 'T_COMMENT',
'content' => '# Comment
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineHashComment */',
[
[
'type' => 'T_COMMENT',
'content' => '# Comment1
',
],
[
'type' => 'T_COMMENT',
'content' => '# Comment2
',
],
[
'type' => 'T_COMMENT',
'content' => '# Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testMultiLineHashCommentWithIndent */',
[
[
'type' => 'T_COMMENT',
'content' => '# Comment1
',
],
[
'type' => 'T_WHITESPACE',
'content' => ' ',
],
[
'type' => 'T_COMMENT',
'content' => '# Comment2
',
],
[
'type' => 'T_WHITESPACE',
'content' => ' ',
],
[
'type' => 'T_COMMENT',
'content' => '# Comment3
',
],
[
'type' => 'T_WHITESPACE',
'content' => '
',
],
],
],
[
'/* testSingleLineHashCommentNoNewLineAtEnd */',
[
[
'type' => 'T_COMMENT',
'content' => '# Hash ',
],
[
'type' => 'T_CLOSE_TAG',
'content' => '?>
',
],
],
],
[
'/* testCommentAtEndOfFile */',
[
[
'type' => 'T_COMMENT',
'content' => '/* Comment',
],
],
],
];
}//end dataCommentTokenization()
}//end class
@@ -0,0 +1,120 @@
<?php
/*
* Type intersection or bitwise and.
*/
/* testBitwiseAnd1 */
$result = $value & $test /* testBitwiseAnd2 */ & $another;
class TypeIntersection
{
/* testTypeIntersectionPropertySimple */
public static Foo&Bar $obj;
/* testTypeIntersectionPropertyReverseModifierOrder */
static protected Something&Nothing $somethingNothing /* testBitwiseAndPropertyDefaultValue */ = E_WARNING & E_NOTICE;
private
/* testTypeIntersectionPropertyMulti1 */
Foo &
/* testTypeIntersectionPropertyMulti2 */
Traversable & // phpcs:ignore Stnd.Cat.Sniff
Boo
/* testTypeIntersectionPropertyMulti3 */
& Bar $multi;
/* testTypeIntersectionPropertyNamespaceRelative */
public namespace\Sub\NameA&namespace\Sub\NameB $namespaceRelative;
/* testTypeIntersectionPropertyPartiallyQualified */
public Partially\Qualified\NameA&Partially\Qualified\NameB $partiallyQual;
/* testTypeIntersectionPropertyFullyQualified */
public \Fully\Qualified\NameA&\Fully\Qualified\NameB $fullyQual;
/* testTypeIntersectionPropertyWithReadOnlyKeyword */
protected readonly Foo&Bar $fooBar;
public function paramTypes(
/* testTypeIntersectionParam1 */
Foo&Bar $paramA /* testBitwiseAndParamDefaultValue */ = CONSTANT_A & CONSTANT_B,
/* testTypeIntersectionParam2 */
Foo&\Bar /* testTypeIntersectionParam3 */ &Baz /* testBitwiseAnd3 */ &...$paramB = null,
) {
/* testBitwiseAnd4 */
return (($a1 ^ $b1) &($a2 ^ $b2)) + $c;
}
public function identifierNames(
/* testTypeIntersectionParamNamespaceRelative */
namespace\Sub\NameA&namespace\Sub\NameB $paramA,
/* testTypeIntersectionParamPartiallyQualified */
Partially\Qualified\NameA&Partially\Qualified\NameB $paramB,
/* testTypeIntersectionParamFullyQualified */
\Fully\Qualified\NameA&\Fully\Qualified\NameB $paramC,
) {}
/* testTypeIntersectionReturnType */
public function returnType() : Foo&Bar {}
/* testTypeIntersectionConstructorPropertyPromotion */
public function __construct( public Foo&Bar $property) {}
/* testTypeIntersectionAbstractMethodReturnType1 */
abstract public function abstractMethod(): Foo&Bar /* testTypeIntersectionAbstractMethodReturnType2 */ &Baz;
/* testTypeIntersectionReturnTypeNamespaceRelative */
public function identifierNamesReturnRelative() : namespace\Sub\NameA&namespace\Sub\NameB {}
/* testTypeIntersectionReturnPartiallyQualified */
public function identifierNamesReturnPQ() : Partially\Qualified\NameA&Partially\Qualified\NameB {}
/* testTypeIntersectionReturnFullyQualified */
public function identifierNamesReturnFQ() : \Fully\Qualified\NameA&\Fully\Qualified\NameB {}
}
/* testTypeIntersectionClosureParamIllegalNullable */
$closureWithParamType = function (?Foo&Bar $string) {};
function globalFunctionWithSpreadAndReference(
/* testTypeIntersectionWithReference */
Foo&Bar /* testBitwiseAnd5 */ &$paramA,
/* testTypeIntersectionWithSpreadOperator */
Foo&Bar ...$paramB
) {}
/* testBitwiseAndClosureParamDefault */
$closureWithReturnType = function ($string = NONSENSE & FAKE)/* testTypeIntersectionClosureReturn */ : \Package\MyA&PackageB {};
/* testTypeIntersectionArrowParam */
$arrowWithParamType = fn (Foo&Bar $param, /* testBitwiseAndArrowParamDefault */ ?int $int = CONSTA & CONSTB )
/* testBitwiseAndArrowExpression */
=> $param & $int;
/* testTypeIntersectionArrowReturnType */
$arrowWithReturnType = fn ($param) : Foo&Bar => $param * 10;
/* testBitwiseAndInArrayKey */
$array = array(
A & B => /* testBitwiseAndInArrayValue */ B & C
);
/* testBitwiseAndInShortArrayKey */
$array = [
A & B => /* testBitwiseAndInShortArrayValue */ B & C
];
/* testBitwiseAndNonArrowFnFunctionCall */
$obj->fn($something & $else);
/* testBitwiseAnd6 */
function &fn(/* testTypeIntersectionNonArrowFunctionDeclaration */ Foo&Bar $something) {}
/* testTypeIntersectionWithInvalidTypes */
function (int&string $var) {};
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
return function( Foo&
@@ -0,0 +1,138 @@
<?php
/**
* Tests the conversion of bitwise and tokens to type intersection tokens.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @author Jaroslav Hanslík <kukulich@kukulich.cz>
* @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 TypeIntersectionTest extends AbstractMethodUnitTest
{
/**
* Test that non-intersection type bitwise and tokens are still tokenized as bitwise and.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataBitwiseAnd
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testBitwiseAnd($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]);
$this->assertSame(T_BITWISE_AND, $tokens[$opener]['code']);
$this->assertSame('T_BITWISE_AND', $tokens[$opener]['type']);
}//end testBitwiseAnd()
/**
* Data provider.
*
* @see testBitwiseAnd()
*
* @return array
*/
public function dataBitwiseAnd()
{
return [
['/* testBitwiseAnd1 */'],
['/* testBitwiseAnd2 */'],
['/* testBitwiseAndPropertyDefaultValue */'],
['/* testBitwiseAndParamDefaultValue */'],
['/* testBitwiseAnd3 */'],
['/* testBitwiseAnd4 */'],
['/* testBitwiseAnd5 */'],
['/* testBitwiseAndClosureParamDefault */'],
['/* testBitwiseAndArrowParamDefault */'],
['/* testBitwiseAndArrowExpression */'],
['/* testBitwiseAndInArrayKey */'],
['/* testBitwiseAndInArrayValue */'],
['/* testBitwiseAndInShortArrayKey */'],
['/* testBitwiseAndInShortArrayValue */'],
['/* testBitwiseAndNonArrowFnFunctionCall */'],
['/* testBitwiseAnd6 */'],
['/* testLiveCoding */'],
];
}//end dataBitwiseAnd()
/**
* Test that bitwise and tokens when used as part of a intersection type are tokenized as `T_TYPE_INTERSECTION`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
* @dataProvider dataTypeIntersection
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testTypeIntersection($testMarker)
{
$tokens = self::$phpcsFile->getTokens();
$opener = $this->getTargetToken($testMarker, [T_BITWISE_AND, T_TYPE_INTERSECTION]);
$this->assertSame(T_TYPE_INTERSECTION, $tokens[$opener]['code']);
$this->assertSame('T_TYPE_INTERSECTION', $tokens[$opener]['type']);
}//end testTypeIntersection()
/**
* Data provider.
*
* @see testTypeIntersection()
*
* @return array
*/
public function dataTypeIntersection()
{
return [
['/* testTypeIntersectionPropertySimple */'],
['/* testTypeIntersectionPropertyReverseModifierOrder */'],
['/* testTypeIntersectionPropertyMulti1 */'],
['/* testTypeIntersectionPropertyMulti2 */'],
['/* testTypeIntersectionPropertyMulti3 */'],
['/* testTypeIntersectionPropertyNamespaceRelative */'],
['/* testTypeIntersectionPropertyPartiallyQualified */'],
['/* testTypeIntersectionPropertyFullyQualified */'],
['/* testTypeIntersectionPropertyWithReadOnlyKeyword */'],
['/* testTypeIntersectionParam1 */'],
['/* testTypeIntersectionParam2 */'],
['/* testTypeIntersectionParam3 */'],
['/* testTypeIntersectionParamNamespaceRelative */'],
['/* testTypeIntersectionParamPartiallyQualified */'],
['/* testTypeIntersectionParamFullyQualified */'],
['/* testTypeIntersectionReturnType */'],
['/* testTypeIntersectionConstructorPropertyPromotion */'],
['/* testTypeIntersectionAbstractMethodReturnType1 */'],
['/* testTypeIntersectionAbstractMethodReturnType2 */'],
['/* testTypeIntersectionReturnTypeNamespaceRelative */'],
['/* testTypeIntersectionReturnPartiallyQualified */'],
['/* testTypeIntersectionReturnFullyQualified */'],
['/* testTypeIntersectionClosureParamIllegalNullable */'],
['/* testTypeIntersectionWithReference */'],
['/* testTypeIntersectionWithSpreadOperator */'],
['/* testTypeIntersectionClosureReturn */'],
['/* testTypeIntersectionArrowParam */'],
['/* testTypeIntersectionArrowReturnType */'],
['/* testTypeIntersectionNonArrowFunctionDeclaration */'],
['/* testTypeIntersectionWithInvalidTypes */'],
];
}//end dataTypeIntersection()
}//end class
@@ -0,0 +1,147 @@
<?php
/* testNamespaceDeclaration */
namespace Package;
/* testNamespaceDeclarationWithLevels */
namespace Vendor\SubLevel\Domain;
/* testUseStatement */
use ClassName;
/* testUseStatementWithLevels */
use Vendor\Level\Domain;
/* testFunctionUseStatement */
use function function_name;
/* testFunctionUseStatementWithLevels */
use function Vendor\Level\function_in_ns;
/* testConstantUseStatement */
use const CONSTANT_NAME;
/* testConstantUseStatementWithLevels */
use const Vendor\Level\OTHER_CONSTANT;
/* testMultiUseUnqualified */
use UnqualifiedClassName,
/* testMultiUsePartiallyQualified */
Sublevel\PartiallyClassName;
/* testGroupUseStatement */
use Vendor\Level\{
AnotherDomain,
function function_grouped,
const CONSTANT_GROUPED,
Sub\YetAnotherDomain,
function SubLevelA\function_grouped_too,
const SubLevelB\CONSTANT_GROUPED_TOO,
};
/* testClassName */
class MyClass
/* testExtendedFQN */
extends \Vendor\Level\FQN
/* testImplementsRelative */
implements namespace\Name,
/* testImplementsFQN */
\Fully\Qualified,
/* testImplementsUnqualified */
Unqualified,
/* testImplementsPartiallyQualified */
Sub\Level\Name
{
/* testFunctionName */
public function function_name(
/* testTypeDeclarationRelative */
?namespace\Name|object $paramA,
/* testTypeDeclarationFQN */
\Fully\Qualified\Name $paramB,
/* testTypeDeclarationUnqualified */
Unqualified|false $paramC,
/* testTypeDeclarationPartiallyQualified */
?Sublevel\Name $paramD,
/* testReturnTypeFQN */
) : ?\Name {
try {
/* testFunctionCallRelative */
echo NameSpace\function_name();
/* testFunctionCallFQN */
echo \Vendor\Package\function_name();
/* testFunctionCallUnqualified */
echo function_name();
/* testFunctionPartiallyQualified */
echo Level\function_name();
/* testCatchRelative */
} catch (namespace\SubLevel\Exception $e) {
/* testCatchFQN */
} catch (\Exception $e) {
/* testCatchUnqualified */
} catch (Exception $e) {
/* testCatchPartiallyQualified */
} catch (Level\Exception $e) {
}
/* testNewRelative */
$obj = new namespace\ClassName();
/* testNewFQN */
$obj = new \Vendor\ClassName();
/* testNewUnqualified */
$obj = new ClassName;
/* testNewPartiallyQualified */
$obj = new Level\ClassName;
/* testDoubleColonRelative */
$value = namespace\ClassName::property;
/* testDoubleColonFQN */
$value = \ClassName::static_function();
/* testDoubleColonUnqualified */
$value = ClassName::CONSTANT_NAME;
/* testDoubleColonPartiallyQualified */
$value = Level\ClassName::CONSTANT_NAME['key'];
/* testInstanceOfRelative */
$is = $obj instanceof namespace\ClassName;
/* testInstanceOfFQN */
if ($obj instanceof \Full\ClassName) {}
/* testInstanceOfUnqualified */
if ($a === $b && $obj instanceof ClassName && true) {}
/* testInstanceOfPartiallyQualified */
$is = $obj instanceof Partially\ClassName;
}
}
/* testInvalidInPHP8Whitespace */
namespace \ Sublevel
\ function_name();
/* testInvalidInPHP8Comments */
$value = \Fully
// phpcs:ignore Stnd.Cat.Sniff -- for reasons
\Qualified
/* comment */
\Name
// comment
:: function_name();
@@ -0,0 +1,94 @@
<?php
/**
* Class to retrieve a filtered file list.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests;
class FileList
{
/**
* The path to the project root directory.
*
* @var string
*/
protected $rootPath;
/**
* Recursive directory iterator.
*
* @var \DirectoryIterator
*/
public $fileIterator;
/**
* Base regex to use if no filter regex is provided.
*
* Matches based on:
* - File path starts with the project root (replacement done in constructor).
* - Don't match .git/ files.
* - Don't match dot files, i.e. "." or "..".
* - Don't match backup files.
* - Match everything else in a case-insensitive manner.
*
* @var string
*/
private $baseRegex = '`^%s(?!\.git/)(?!(.*/)?\.+$)(?!.*\.(bak|orig)).*$`Dix';
/**
* Constructor.
*
* @param string $directory The directory to examine.
* @param string $rootPath Path to the project root.
* @param string $filter PCRE regular expression to filter the file list with.
*/
public function __construct($directory, $rootPath='', $filter='')
{
$this->rootPath = $rootPath;
$directory = new \RecursiveDirectoryIterator(
$directory,
\RecursiveDirectoryIterator::UNIX_PATHS
);
$flattened = new \RecursiveIteratorIterator(
$directory,
\RecursiveIteratorIterator::LEAVES_ONLY,
\RecursiveIteratorIterator::CATCH_GET_CHILD
);
if ($filter === '') {
$filter = sprintf($this->baseRegex, preg_quote($this->rootPath));
}
$this->fileIterator = new \RegexIterator($flattened, $filter);
return $this;
}//end __construct()
/**
* Retrieve the filtered file list as an array.
*
* @return array
*/
public function getList()
{
$fileList = [];
foreach ($this->fileIterator as $file) {
$fileList[] = str_replace($this->rootPath, '', $file);
}
return $fileList;
}//end getList()
}//end class
@@ -0,0 +1,461 @@
<?php
/**
* An abstract class that all sniff unit tests must extend.
*
* A sniff unit test checks a .inc file for expected violations of a single
* coding standard. Expected errors and warnings that are not found, or
* warnings and errors that are not expected, are considered test failures.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\Standards;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Files\LocalFile;
use PHP_CodeSniffer\Util\Common;
use PHPUnit\Framework\TestCase;
abstract class AbstractSniffUnitTest extends TestCase
{
/**
* Enable or disable the backup and restoration of the $GLOBALS array.
* Overwrite this attribute in a child class of TestCase.
* Setting this attribute in setUp() has no effect!
*
* @var boolean
*/
protected $backupGlobals = false;
/**
* The path to the standard's main directory.
*
* @var string
*/
public $standardsDir = null;
/**
* The path to the standard's test directory.
*
* @var string
*/
public $testsDir = null;
/**
* Sets up this unit test.
*
* @return void
*/
protected function setUp()
{
$class = get_class($this);
$this->standardsDir = $GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$class];
$this->testsDir = $GLOBALS['PHP_CODESNIFFER_TEST_DIRS'][$class];
}//end setUp()
/**
* Get a list of all test files to check.
*
* These will have the same base as the sniff name but different extensions.
* We ignore the .php file as it is the class.
*
* @param string $testFileBase The base path that the unit tests files will have.
*
* @return string[]
*/
protected function getTestFiles($testFileBase)
{
$testFiles = [];
$dir = substr($testFileBase, 0, strrpos($testFileBase, DIRECTORY_SEPARATOR));
$di = new \DirectoryIterator($dir);
foreach ($di as $file) {
$path = $file->getPathname();
if (substr($path, 0, strlen($testFileBase)) === $testFileBase) {
if ($path !== $testFileBase.'php' && substr($path, -5) !== 'fixed' && substr($path, -4) !== '.bak') {
$testFiles[] = $path;
}
}
}
// Put them in order.
sort($testFiles);
return $testFiles;
}//end getTestFiles()
/**
* Should this test be skipped for some reason.
*
* @return boolean
*/
protected function shouldSkipTest()
{
return false;
}//end shouldSkipTest()
/**
* Tests the extending classes Sniff class.
*
* @return void
* @throws \PHPUnit\Framework\Exception
*/
final public function testSniff()
{
// Skip this test if we can't run in this environment.
if ($this->shouldSkipTest() === true) {
$this->markTestSkipped();
}
$sniffCode = Common::getSniffCode(get_class($this));
list($standardName, $categoryName, $sniffName) = explode('.', $sniffCode);
$testFileBase = $this->testsDir.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'UnitTest.';
// Get a list of all test files to check.
$testFiles = $this->getTestFiles($testFileBase);
$GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES'][] = $testFiles;
if (isset($GLOBALS['PHP_CODESNIFFER_CONFIG']) === true) {
$config = $GLOBALS['PHP_CODESNIFFER_CONFIG'];
} else {
$config = new Config();
$config->cache = false;
$GLOBALS['PHP_CODESNIFFER_CONFIG'] = $config;
}
$config->standards = [$standardName];
$config->sniffs = [$sniffCode];
$config->ignored = [];
if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS']) === false) {
$GLOBALS['PHP_CODESNIFFER_RULESETS'] = [];
}
if (isset($GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName]) === false) {
$ruleset = new Ruleset($config);
$GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName] = $ruleset;
}
$ruleset = $GLOBALS['PHP_CODESNIFFER_RULESETS'][$standardName];
$sniffFile = $this->standardsDir.DIRECTORY_SEPARATOR.'Sniffs'.DIRECTORY_SEPARATOR.$categoryName.DIRECTORY_SEPARATOR.$sniffName.'Sniff.php';
$sniffClassName = substr(get_class($this), 0, -8).'Sniff';
$sniffClassName = str_replace('\Tests\\', '\Sniffs\\', $sniffClassName);
$sniffClassName = Common::cleanSniffClass($sniffClassName);
$restrictions = [strtolower($sniffClassName) => true];
$ruleset->registerSniffs([$sniffFile], $restrictions, []);
$ruleset->populateTokenListeners();
$failureMessages = [];
foreach ($testFiles as $testFile) {
$filename = basename($testFile);
$oldConfig = $config->getSettings();
try {
$this->setCliValues($filename, $config);
$phpcsFile = new LocalFile($testFile, $ruleset, $config);
$phpcsFile->process();
} catch (RuntimeException $e) {
$this->fail('An unexpected exception has been caught: '.$e->getMessage());
}
$failures = $this->generateFailureMessages($phpcsFile);
$failureMessages = array_merge($failureMessages, $failures);
if ($phpcsFile->getFixableCount() > 0) {
// Attempt to fix the errors.
$phpcsFile->fixer->fixFile();
$fixable = $phpcsFile->getFixableCount();
if ($fixable > 0) {
$failureMessages[] = "Failed to fix $fixable fixable violations in $filename";
}
// Check for a .fixed file to check for accuracy of fixes.
$fixedFile = $testFile.'.fixed';
if (file_exists($fixedFile) === true) {
$diff = $phpcsFile->fixer->generateDiff($fixedFile);
if (trim($diff) !== '') {
$filename = basename($testFile);
$fixedFilename = basename($fixedFile);
$failureMessages[] = "Fixed version of $filename does not match expected version in $fixedFilename; the diff is\n$diff";
}
}
}
// Restore the config.
$config->setSettings($oldConfig);
}//end foreach
if (empty($failureMessages) === false) {
$this->fail(implode(PHP_EOL, $failureMessages));
}
}//end testSniff()
/**
* Generate a list of test failures for a given sniffed file.
*
* @param \PHP_CodeSniffer\Files\LocalFile $file The file being tested.
*
* @return array
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException
*/
public function generateFailureMessages(LocalFile $file)
{
$testFile = $file->getFilename();
$foundErrors = $file->getErrors();
$foundWarnings = $file->getWarnings();
$expectedErrors = $this->getErrorList(basename($testFile));
$expectedWarnings = $this->getWarningList(basename($testFile));
if (is_array($expectedErrors) === false) {
throw new RuntimeException('getErrorList() must return an array');
}
if (is_array($expectedWarnings) === false) {
throw new RuntimeException('getWarningList() must return an array');
}
/*
We merge errors and warnings together to make it easier
to iterate over them and produce the errors string. In this way,
we can report on errors and warnings in the same line even though
it's not really structured to allow that.
*/
$allProblems = [];
$failureMessages = [];
foreach ($foundErrors as $line => $lineErrors) {
foreach ($lineErrors as $column => $errors) {
if (isset($allProblems[$line]) === false) {
$allProblems[$line] = [
'expected_errors' => 0,
'expected_warnings' => 0,
'found_errors' => [],
'found_warnings' => [],
];
}
$foundErrorsTemp = [];
foreach ($allProblems[$line]['found_errors'] as $foundError) {
$foundErrorsTemp[] = $foundError;
}
$errorsTemp = [];
foreach ($errors as $foundError) {
$errorsTemp[] = $foundError['message'].' ('.$foundError['source'].')';
$source = $foundError['source'];
if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'], true) === false) {
$GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source;
}
if ($foundError['fixable'] === true
&& in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'], true) === false
) {
$GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source;
}
}
$allProblems[$line]['found_errors'] = array_merge($foundErrorsTemp, $errorsTemp);
}//end foreach
if (isset($expectedErrors[$line]) === true) {
$allProblems[$line]['expected_errors'] = $expectedErrors[$line];
} else {
$allProblems[$line]['expected_errors'] = 0;
}
unset($expectedErrors[$line]);
}//end foreach
foreach ($expectedErrors as $line => $numErrors) {
if (isset($allProblems[$line]) === false) {
$allProblems[$line] = [
'expected_errors' => 0,
'expected_warnings' => 0,
'found_errors' => [],
'found_warnings' => [],
];
}
$allProblems[$line]['expected_errors'] = $numErrors;
}
foreach ($foundWarnings as $line => $lineWarnings) {
foreach ($lineWarnings as $column => $warnings) {
if (isset($allProblems[$line]) === false) {
$allProblems[$line] = [
'expected_errors' => 0,
'expected_warnings' => 0,
'found_errors' => [],
'found_warnings' => [],
];
}
$foundWarningsTemp = [];
foreach ($allProblems[$line]['found_warnings'] as $foundWarning) {
$foundWarningsTemp[] = $foundWarning;
}
$warningsTemp = [];
foreach ($warnings as $warning) {
$warningsTemp[] = $warning['message'].' ('.$warning['source'].')';
$source = $warning['source'];
if (in_array($source, $GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'], true) === false) {
$GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'][] = $source;
}
if ($warning['fixable'] === true
&& in_array($source, $GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'], true) === false
) {
$GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'][] = $source;
}
}
$allProblems[$line]['found_warnings'] = array_merge($foundWarningsTemp, $warningsTemp);
}//end foreach
if (isset($expectedWarnings[$line]) === true) {
$allProblems[$line]['expected_warnings'] = $expectedWarnings[$line];
} else {
$allProblems[$line]['expected_warnings'] = 0;
}
unset($expectedWarnings[$line]);
}//end foreach
foreach ($expectedWarnings as $line => $numWarnings) {
if (isset($allProblems[$line]) === false) {
$allProblems[$line] = [
'expected_errors' => 0,
'expected_warnings' => 0,
'found_errors' => [],
'found_warnings' => [],
];
}
$allProblems[$line]['expected_warnings'] = $numWarnings;
}
// Order the messages by line number.
ksort($allProblems);
foreach ($allProblems as $line => $problems) {
$numErrors = count($problems['found_errors']);
$numWarnings = count($problems['found_warnings']);
$expectedErrors = $problems['expected_errors'];
$expectedWarnings = $problems['expected_warnings'];
$errors = '';
$foundString = '';
if ($expectedErrors !== $numErrors || $expectedWarnings !== $numWarnings) {
$lineMessage = "[LINE $line]";
$expectedMessage = 'Expected ';
$foundMessage = 'in '.basename($testFile).' but found ';
if ($expectedErrors !== $numErrors) {
$expectedMessage .= "$expectedErrors error(s)";
$foundMessage .= "$numErrors error(s)";
if ($numErrors !== 0) {
$foundString .= 'error(s)';
$errors .= implode(PHP_EOL.' -> ', $problems['found_errors']);
}
if ($expectedWarnings !== $numWarnings) {
$expectedMessage .= ' and ';
$foundMessage .= ' and ';
if ($numWarnings !== 0) {
if ($foundString !== '') {
$foundString .= ' and ';
}
}
}
}
if ($expectedWarnings !== $numWarnings) {
$expectedMessage .= "$expectedWarnings warning(s)";
$foundMessage .= "$numWarnings warning(s)";
if ($numWarnings !== 0) {
$foundString .= 'warning(s)';
if (empty($errors) === false) {
$errors .= PHP_EOL.' -> ';
}
$errors .= implode(PHP_EOL.' -> ', $problems['found_warnings']);
}
}
$fullMessage = "$lineMessage $expectedMessage $foundMessage.";
if ($errors !== '') {
$fullMessage .= " The $foundString found were:".PHP_EOL." -> $errors";
}
$failureMessages[] = $fullMessage;
}//end if
}//end foreach
return $failureMessages;
}//end generateFailureMessages()
/**
* Get a list of CLI values to set before the file is tested.
*
* @param string $filename The name of the file being tested.
* @param \PHP_CodeSniffer\Config $config The config data for the run.
*
* @return void
*/
public function setCliValues($filename, $config)
{
return;
}//end setCliValues()
/**
* Returns the lines where errors should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of errors that should occur on that line.
*
* @return array<int, int>
*/
abstract protected function getErrorList();
/**
* Returns the lines where warnings should occur.
*
* The key of the array should represent the line number and the value
* should represent the number of warnings that should occur on that line.
*
* @return array<int, int>
*/
abstract protected function getWarningList();
}//end class
@@ -0,0 +1,123 @@
<?php
/**
* A test class for testing all sniffs for installed standards.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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\Standards;
use PHP_CodeSniffer\Util\Standards;
use PHP_CodeSniffer\Autoload;
use PHPUnit\TextUI\TestRunner;
use PHPUnit\Framework\TestSuite;
class AllSniffs
{
/**
* Prepare the test runner.
*
* @return void
*/
public static function main()
{
TestRunner::run(self::suite());
}//end main()
/**
* Add all sniff unit tests into a test suite.
*
* Sniff unit tests are found by recursing through the 'Tests' directory
* of each installed coding standard.
*
* @return \PHPUnit\Framework\TestSuite
*/
public static function suite()
{
$GLOBALS['PHP_CODESNIFFER_SNIFF_CODES'] = [];
$GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES'] = [];
$GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES'] = [];
$suite = new TestSuite('PHP CodeSniffer Standards');
$isInstalled = !is_file(__DIR__.'/../../autoload.php');
// Optionally allow for ignoring the tests for one or more standards.
$ignoreTestsForStandards = getenv('PHPCS_IGNORE_TESTS');
if ($ignoreTestsForStandards === false) {
$ignoreTestsForStandards = [];
} else {
$ignoreTestsForStandards = explode(',', $ignoreTestsForStandards);
}
$installedStandards = self::getInstalledStandardDetails();
foreach ($installedStandards as $standard => $details) {
Autoload::addSearchPath($details['path'], $details['namespace']);
// If the test is running PEAR installed, the built-in standards
// are split into different directories; one for the sniffs and
// a different file system location for tests.
if ($isInstalled === true && is_dir(dirname($details['path']).DIRECTORY_SEPARATOR.'Generic') === true) {
$testPath = realpath(__DIR__.'/../../src/Standards/'.$standard);
} else {
$testPath = $details['path'];
}
if (in_array($standard, $ignoreTestsForStandards, true) === true) {
continue;
}
$testsDir = $testPath.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR;
if (is_dir($testsDir) === false) {
// No tests for this standard.
continue;
}
$di = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($testsDir));
foreach ($di as $file) {
// Skip hidden files.
if (substr($file->getFilename(), 0, 1) === '.') {
continue;
}
// Tests must have the extension 'php'.
$parts = explode('.', $file);
$ext = array_pop($parts);
if ($ext !== 'php') {
continue;
}
$className = Autoload::loadFile($file->getPathname());
$GLOBALS['PHP_CODESNIFFER_STANDARD_DIRS'][$className] = $details['path'];
$GLOBALS['PHP_CODESNIFFER_TEST_DIRS'][$className] = $testsDir;
$suite->addTestSuite($className);
}
}//end foreach
return $suite;
}//end suite()
/**
* Get the details of all coding standards installed.
*
* @return array
* @see Standards::getInstalledStandardDetails()
*/
protected static function getInstalledStandardDetails()
{
return Standards::getInstalledStandardDetails(true);
}//end getInstalledStandardDetails()
}//end class
@@ -0,0 +1,35 @@
<?php
/**
* A PHP_CodeSniffer specific test suite for PHPUnit.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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;
use PHPUnit\Framework\TestSuite as PHPUnit_TestSuite;
use PHPUnit\Framework\TestResult;
class TestSuite extends PHPUnit_TestSuite
{
/**
* Runs the tests and collects their result in a TestResult.
*
* @param \PHPUnit\Framework\TestResult $result A test result.
*
* @return \PHPUnit\Framework\TestResult
*/
public function run(TestResult $result=null)
{
$result = parent::run($result);
printPHPCodeSnifferTestOutput();
return $result;
}//end run()
}//end class
@@ -0,0 +1,35 @@
<?php
/**
* A PHP_CodeSniffer specific test suite for PHPUnit.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 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;
use PHPUnit\Framework\TestSuite as PHPUnit_TestSuite;
use PHPUnit\Framework\TestResult;
class TestSuite extends PHPUnit_TestSuite
{
/**
* Runs the tests and collects their result in a TestResult.
*
* @param \PHPUnit\Framework\TestResult $result A test result.
*
* @return \PHPUnit\Framework\TestResult
*/
public function run(TestResult $result=null): TestResult
{
$result = parent::run($result);
printPHPCodeSnifferTestOutput();
return $result;
}//end run()
}//end class
@@ -0,0 +1,91 @@
<?php
/**
* Bootstrap file for PHP_CodeSniffer unit tests.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2017 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
if (defined('PHP_CODESNIFFER_IN_TESTS') === false) {
define('PHP_CODESNIFFER_IN_TESTS', true);
}
if (defined('PHP_CODESNIFFER_CBF') === false) {
define('PHP_CODESNIFFER_CBF', false);
}
if (defined('PHP_CODESNIFFER_VERBOSITY') === false) {
define('PHP_CODESNIFFER_VERBOSITY', 0);
}
if (is_file(__DIR__.'/../autoload.php') === true) {
include_once __DIR__.'/../autoload.php';
} else {
include_once 'PHP/CodeSniffer/autoload.php';
}
$tokens = new \PHP_CodeSniffer\Util\Tokens();
// Compatibility for PHPUnit < 6 and PHPUnit 6+.
if (class_exists('PHPUnit_Framework_TestSuite') === true && class_exists('PHPUnit\Framework\TestSuite') === false) {
class_alias('PHPUnit_Framework_TestSuite', 'PHPUnit'.'\Framework\TestSuite');
}
if (class_exists('PHPUnit_Framework_TestCase') === true && class_exists('PHPUnit\Framework\TestCase') === false) {
class_alias('PHPUnit_Framework_TestCase', 'PHPUnit'.'\Framework\TestCase');
}
if (class_exists('PHPUnit_TextUI_TestRunner') === true && class_exists('PHPUnit\TextUI\TestRunner') === false) {
class_alias('PHPUnit_TextUI_TestRunner', 'PHPUnit'.'\TextUI\TestRunner');
}
if (class_exists('PHPUnit_Framework_TestResult') === true && class_exists('PHPUnit\Framework\TestResult') === false) {
class_alias('PHPUnit_Framework_TestResult', 'PHPUnit'.'\Framework\TestResult');
}
// Determine whether this is a PEAR install or not.
$GLOBALS['PHP_CODESNIFFER_PEAR'] = false;
if (is_file(__DIR__.'/../autoload.php') === false) {
$GLOBALS['PHP_CODESNIFFER_PEAR'] = true;
}
/**
* A global util function to help print unit test fixing data.
*
* @return void
*/
function printPHPCodeSnifferTestOutput()
{
echo PHP_EOL.PHP_EOL;
$output = 'The test files';
$data = [];
$codeCount = count($GLOBALS['PHP_CODESNIFFER_SNIFF_CODES']);
if (empty($GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES']) === false) {
$files = call_user_func_array('array_merge', $GLOBALS['PHP_CODESNIFFER_SNIFF_CASE_FILES']);
$files = array_unique($files);
$fileCount = count($files);
$output = '%d sniff test files';
$data[] = $fileCount;
}
$output .= ' generated %d unique error codes';
$data[] = $codeCount;
if ($codeCount > 0) {
$fixes = count($GLOBALS['PHP_CODESNIFFER_FIXABLE_CODES']);
$percent = round(($fixes / $codeCount * 100), 2);
$output .= '; %d were fixable (%d%%)';
$data[] = $fixes;
$data[] = $percent;
}
vprintf($output, $data);
}//end printPHPCodeSnifferTestOutput()