基础代码

This commit is contained in:
2021-02-26 22:23:13 +08:00
parent 7884df52f0
commit a719feebba
2585 changed files with 328263 additions and 0 deletions
+64
View File
@@ -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,118 @@
<?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\Autoloader;
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 = \PHP_CodeSniffer\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 = \PHP_CodeSniffer\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 = \PHP_CodeSniffer\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 = \PHP_CodeSniffer\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 {}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
<?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;
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
/* testArrowFunctionAsArgument */
$foo = foo(
fn() => bar()
);
/* testArrowFunctionWithArrayAsArgument */
$foo = foo(
fn() => [$row[0], $row[3]]
);
return 0;
@@ -0,0 +1,240 @@
<?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()
}//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,26 @@
<?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 {}
@@ -0,0 +1,89 @@
<?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]);
$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',
],
],
];
}//end dataImplementedInterface()
}//end class
@@ -0,0 +1,195 @@
<?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;
}
@@ -0,0 +1,554 @@
<?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,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testVarType */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'type' => '?int',
'nullable_type' => true,
],
],
[
'/* testPublic */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'string',
'nullable_type' => false,
],
],
[
'/* testProtected */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedType */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'type' => 'bool',
'nullable_type' => false,
],
],
[
'/* testPrivate */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateType */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => 'array',
'nullable_type' => false,
],
],
[
'/* testStatic */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testStaticType */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testStaticVar */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testVarStatic */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicStatic */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedStatic */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateStatic */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPublicStaticWithDocblock */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testProtectedStaticWithDocblock */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPrivateStaticWithDocblock */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupType 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'float',
'nullable_type' => false,
],
],
[
'/* testGroupType 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'float',
'nullable_type' => false,
],
],
[
'/* testGroupNullableType 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testGroupNullableType 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'type' => '?string',
'nullable_type' => true,
],
],
[
'/* testNoPrefix */',
[
'scope' => 'public',
'scope_specified' => false,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupProtectedStatic 1 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupProtectedStatic 2 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupProtectedStatic 3 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 1 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 2 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 3 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 4 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 5 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 6 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testGroupPrivate 7 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testMessyNullableType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '?array',
'nullable_type' => true,
],
],
[
'/* testNamespaceType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '\MyNamespace\MyClass',
'nullable_type' => false,
],
],
[
'/* testNullableNamespaceType 1 */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '?ClassName',
'nullable_type' => true,
],
],
[
'/* testNullableNamespaceType 2 */',
[
'scope' => 'protected',
'scope_specified' => true,
'is_static' => false,
'type' => '?Folder\ClassName',
'nullable_type' => true,
],
],
[
'/* testMultilineNamespaceType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '\MyNamespace\MyClass\Foo',
'nullable_type' => false,
],
],
[
'/* testPropertyAfterMethod */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => true,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testInterfaceProperty */',
[],
],
[
'/* testNestedProperty 1 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testNestedProperty 2 */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '',
'nullable_type' => false,
],
],
[
'/* testPHP8MixedTypeHint */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => true,
'type' => 'miXed',
'nullable_type' => false,
],
],
[
'/* testPHP8MixedTypeHintNullable */',
[
'scope' => 'private',
'scope_specified' => true,
'is_static' => false,
'type' => '?mixed',
'nullable_type' => true,
],
],
[
'/* testNamespaceOperatorTypeHint */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '?namespace\Name',
'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 */'],
];
}//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,43 @@
<?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) {}
@@ -0,0 +1,361 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:getMethodParameters 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 GetMethodParametersTest extends AbstractMethodUnitTest
{
/**
* Verify pass-by-reference parsing.
*
* @return void
*/
public function testPassByReference()
{
$expected = [];
$expected[0] = [
'name' => '$var',
'content' => '&$var',
'pass_by_reference' => true,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPassByReference()
/**
* Verify array hint parsing.
*
* @return void
*/
public function testArrayHint()
{
$expected = [];
$expected[0] = [
'name' => '$var',
'content' => 'array $var',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'array',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testArrayHint()
/**
* Verify type hint parsing.
*
* @return void
*/
public function testTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => 'foo $var1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'foo',
'nullable_type' => false,
];
$expected[1] = [
'name' => '$var2',
'content' => 'bar $var2',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'bar',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testTypeHint()
/**
* Verify self type hint parsing.
*
* @return void
*/
public function testSelfTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var',
'content' => 'self $var',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'self',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testSelfTypeHint()
/**
* Verify nullable type hint parsing.
*
* @return void
*/
public function testNullableTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '?int $var1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?int',
'nullable_type' => true,
];
$expected[1] = [
'name' => '$var2',
'content' => '?\bar $var2',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?\bar',
'nullable_type' => true,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testNullableTypeHint()
/**
* Verify variable.
*
* @return void
*/
public function testVariable()
{
$expected = [];
$expected[0] = [
'name' => '$var',
'content' => '$var',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testVariable()
/**
* Verify default value parsing with a single function param.
*
* @return void
*/
public function testSingleDefaultValue()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '$var1=self::CONSTANT',
'default' => 'self::CONSTANT',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testSingleDefaultValue()
/**
* Verify default value parsing.
*
* @return void
*/
public function testDefaultValues()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '$var1=1',
'default' => '1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$expected[1] = [
'name' => '$var2',
'content' => "\$var2='value'",
'default' => "'value'",
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testDefaultValues()
/**
* Verify "bitwise and" in default value !== pass-by-reference.
*
* @return void
*/
public function testBitwiseAndConstantExpressionDefaultValue()
{
$expected = [];
$expected[0] = [
'name' => '$a',
'content' => '$a = 10 & 20',
'default' => '10 & 20',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testBitwiseAndConstantExpressionDefaultValue()
/**
* Verify that arrow functions are supported.
*
* @return void
*/
public function testArrowFunction()
{
$expected = [];
$expected[0] = [
'name' => '$a',
'content' => 'int $a',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'int',
'nullable_type' => false,
];
$expected[1] = [
'name' => '$b',
'content' => '...$b',
'pass_by_reference' => false,
'variable_length' => true,
'type_hint' => '',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testArrowFunction()
/**
* Verify recognition of PHP8 mixed type declaration.
*
* @return void
*/
public function testPHP8MixedTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => 'mixed &...$var1',
'pass_by_reference' => true,
'variable_length' => true,
'type_hint' => 'mixed',
'nullable_type' => false,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8MixedTypeHint()
/**
* Verify recognition of PHP8 mixed type declaration with nullability.
*
* @return void
*/
public function testPHP8MixedTypeHintNullable()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '?Mixed $var1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?Mixed',
'nullable_type' => true,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testPHP8MixedTypeHintNullable()
/**
* Verify recognition of type declarations using the namespace operator.
*
* @return void
*/
public function testNamespaceOperatorTypeHint()
{
$expected = [];
$expected[0] = [
'name' => '$var1',
'content' => '?namespace\Name $var1',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?namespace\Name',
'nullable_type' => true,
];
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
}//end testNamespaceOperatorTypeHint()
/**
* Test helper.
*
* @param string $commentString The comment which preceeds the test.
* @param array $expected The expected function output.
*
* @return void
*/
private function getMethodParametersTestHelper($commentString, $expected)
{
$function = $this->getTargetToken($commentString, [T_FUNCTION, T_FN]);
$found = self::$phpcsFile->getMethodParameters($function);
$this->assertArraySubset($expected, $found, true);
}//end getMethodParametersTestHelper()
}//end class
@@ -0,0 +1,85 @@
<?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 {}
@@ -0,0 +1,496 @@
<?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()
/**
* 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 myFunc($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,135 @@
<?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;
/* 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);
$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,
];
/* 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;
/* testTernary */
$fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b';
/* 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);
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$fn = fn
@@ -0,0 +1,749 @@
<?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()
{
$tokens = self::$phpcsFile->getTokens();
foreach (['/* testStandard */', '/* testMixedCase */'] as $comment) {
$token = $this->getTargetToken($comment, T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the semicolon token');
}
}//end testSimple()
/**
* Test whitespace inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testWhitespace()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testWhitespace */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 13), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 13), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 13), 'Closer scope closer is not the semicolon token');
}//end testWhitespace()
/**
* Test comments inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testComment()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testComment */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the semicolon token');
}//end testComment()
/**
* Test heredocs inside arrow function definitions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testHeredoc()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testHeredoc */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 4), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token');
}//end testHeredoc()
/**
* Test nested arrow functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedOuter()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNestedOuter */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 25), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 25), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 25), 'Closer scope closer is not the semicolon token');
}//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);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 16), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 16), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token - 4), 'Closer scope opener is not the arrow token of the "outer" arrow function (shared scope closer)');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 16), 'Closer scope closer is not the semicolon token');
}//end testNestedInner()
/**
* Test arrow functions that call functions.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFunctionCall()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testFunctionCall */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 17), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token');
}//end testFunctionCall()
/**
* Test arrow functions that are included in chained calls.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testChainedFunctionCall()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testChainedFunctionCall */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer is not the bracket token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer is not the bracket token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer is not the bracket token');
}//end testChainedFunctionCall()
/**
* Test arrow functions that are used as function arguments.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testFunctionArgument()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testFunctionArgument */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 15), 'Scope closer is not the comma token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 15), 'Opener scope closer is not the comma token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 15), 'Closer scope closer is not the comma token');
}//end testFunctionArgument()
/**
* Test arrow functions that use closures.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testClosure()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testClosure */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 60), 'Scope closer is not the comma token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 60), 'Opener scope closer is not the comma token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 60), 'Closer scope closer is not the comma token');
}//end testClosure()
/**
* Test arrow functions with a return type.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReturnType()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testReturnType */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 11), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the comma token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the comma token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the comma token');
}//end testReturnType()
/**
* Test arrow functions that return a reference.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testReference()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testReference */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 6), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 6), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 6), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the semicolon token');
}//end testReference()
/**
* Test arrow functions that are grouped by parenthesis.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testGrouped()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testGrouped */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 8), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 8), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 8), 'Closer scope closer is not the semicolon token');
}//end testGrouped()
/**
* Test arrow functions that are used as array values.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testArrayValue()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testArrayValue */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 4), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 9), 'Scope closer is not the comma token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 4), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 9), 'Opener scope closer is not the comma token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 4), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 9), 'Closer scope closer is not the comma token');
}//end testArrayValue()
/**
* Test arrow functions that use the yield keyword.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testYield()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testYield */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 14), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), 'Closer scope closer is not the semicolon token');
}//end testYield()
/**
* Test arrow functions that use nullable namespace types.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNullableNamespace()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNullableNamespace */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 15), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 15), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 15), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token');
}//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()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNamespaceOperatorInTypes */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 16), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 19), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 16), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 19), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 16), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 19), 'Closer scope closer is not the semicolon token');
}//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);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 11), "Scope opener is not the arrow token (for $marker)");
$this->assertSame($tokens[$token]['scope_closer'], ($token + 14), "Scope closer is not the semicolon token(for $marker)");
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), "Opener scope opener is not the arrow token(for $marker)");
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 14), "Opener scope closer is not the semicolon token(for $marker)");
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), "Closer scope opener is not the arrow token(for $marker)");
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 14), "Closer scope closer is not the semicolon token(for $marker)");
}
}//end testKeywordReturnTypes()
/**
* 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->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 40), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 40), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 40), 'Closer scope closer is not the semicolon token');
$token = $this->getTargetToken('/* testTernaryThen */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for THEN is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 12), 'Scope closer for THEN is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for THEN is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 12), 'Opener scope closer for THEN is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 8), 'Closer scope opener for THEN is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 12), 'Closer scope closer for THEN is not the semicolon token');
$token = $this->getTargetToken('/* testTernaryElse */', T_FN);
$this->backfillHelper($token, true);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 8), 'Scope opener for ELSE is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 11), 'Scope closer for ELSE is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 8), 'Opener scope opener for ELSE is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 11), 'Opener scope closer for ELSE is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token - 24), 'Closer scope opener for ELSE is not the arrow token of the "outer" arrow function (shared scope closer)');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 11), 'Closer scope closer for ELSE is not the semicolon token');
}//end testTernary()
/**
* Test arrow function nested within a method declaration.
*
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
*
* @return void
*/
public function testNestedInMethod()
{
$tokens = self::$phpcsFile->getTokens();
$token = $this->getTargetToken('/* testNestedInMethod */', T_FN);
$this->backfillHelper($token);
$this->assertSame($tokens[$token]['scope_opener'], ($token + 5), 'Scope opener is not the arrow token');
$this->assertSame($tokens[$token]['scope_closer'], ($token + 17), 'Scope closer is not the semicolon token');
$opener = $tokens[$token]['scope_opener'];
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 5), 'Opener scope opener is not the arrow token');
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 17), 'Opener scope closer is not the semicolon token');
$closer = $tokens[$token]['scope_closer'];
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 5), 'Closer scope opener is not the arrow token');
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 17), 'Closer scope closer is not the semicolon token');
}//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 */'],
['/* testLiveCoding */'],
];
}//end dataNotAnArrowFunction()
/**
* Helper function to check that all token keys are correctly set for T_FN tokens.
*
* @param string $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');
$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()
}//end class
@@ -0,0 +1,82 @@
<?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;
/* 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;
/*
* 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,380 @@
<?php
/**
* Tests the backfilling of numeric seperators 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 seperators 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' => '/* testIntMoreThanMax */',
'type' => $testIntMoreThanMaxType,
'value' => '10_223_372_036_854_775_807',
],
],
];
}//end dataTestBackfill()
/**
* Test that numbers using numeric seperators 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',
],
],
],
[
'/* 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,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,94 @@
<?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];
/*
* 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]);
/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$array = [
@@ -0,0 +1,131 @@
<?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 [
['/* testArrayAccess1 */'],
['/* testArrayAccess2 */'],
['/* testArrayAssignment */'],
['/* testFunctionCallDereferencing */'],
['/* testMethodCallDereferencing */'],
['/* testStaticMethodCallDereferencing */'],
['/* testPropertyDereferencing */'],
['/* testPropertyDereferencingWithInaccessibleName */'],
['/* testStaticPropertyDereferencing */'],
['/* testStringDereferencing */'],
['/* testStringDereferencingDoubleQuoted */'],
['/* testConstantDereferencing */'],
['/* testClassConstantDereferencing */'],
['/* testMagicConstantDereferencing */'],
['/* testArrayAccessCurlyBraces */'],
['/* testArrayLiteralDereferencing */'],
['/* testShortArrayLiteralDereferencing */'],
['/* testClassMemberDereferencingOnInstantiation1 */'],
['/* testClassMemberDereferencingOnInstantiation2 */'],
['/* testClassMemberDereferencingOnClone */'],
['/* testNullsafeMethodCallDereferencing */'],
['/* 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 [
['/* testShortArrayDeclarationEmpty */'],
['/* testShortArrayDeclarationWithOneValue */'],
['/* testShortArrayDeclarationWithMultipleValues */'],
['/* testShortArrayDeclarationWithDereferencing */'],
['/* testShortListDeclaration */'],
['/* testNestedListDeclaration */'],
['/* testArrayWithinFunctionCall */'],
];
}//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
File diff suppressed because it is too large Load Diff
@@ -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,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();
File diff suppressed because it is too large Load Diff
+94
View File
@@ -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
+35
View File
@@ -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
+35
View File
@@ -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
+91
View File
@@ -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()