first commit

This commit is contained in:
Claudecio Martins
2025-11-04 18:22:02 +01:00
commit c1184d2878
4394 changed files with 444123 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests;
require_once 'Core/AllTests.php';
require_once 'Standards/AllSniffs.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_compare($version, '7.0', '>=') === true) {
$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

View File

@@ -0,0 +1,213 @@
<?php
/**
* Config class for use in the tests.
*
* The Config class contains a number of static properties.
* As the value of these static properties will be retained between instantiations of the class,
* config values set in one test can influence the results for another test, which makes tests unstable.
*
* This class is a "double" of the Config class which prevents this from happening.
* In _most_ cases, tests should be using this class instead of the "normal" Config,
* with the exception of select tests for the Config class itself.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2024 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests;
use PHP_CodeSniffer\Config;
use ReflectionProperty;
final class ConfigDouble extends Config
{
/**
* Whether or not the setting of a standard should be skipped.
*
* @var boolean
*/
private $skipSettingStandard = false;
/**
* Creates a clean Config object and populates it with command line values.
*
* @param array<string> $cliArgs An array of values gathered from CLI args.
* @param bool $skipSettingStandard Whether to skip setting a standard to prevent
* the Config class trying to auto-discover a ruleset file.
* Should only be set to `true` for tests which actually test
* the ruleset auto-discovery.
* Note: there is no need to set this to `true` when a standard
* is being passed via the `$cliArgs`. Those settings will always
* be respected.
* Defaults to `false`. Will result in the standard being set
* to "PSR1" if not provided via `$cliArgs`.
* @param bool $skipSettingReportWidth Whether to skip setting a report-width to prevent
* the Config class trying to auto-discover the screen width.
* Should only be set to `true` for tests which actually test
* the screen width auto-discovery.
* Note: there is no need to set this to `true` when a report-width
* is being passed via the `$cliArgs`. Those settings will always
* respected.
* Defaults to `false`. Will result in the reportWidth being set
* to "80" if not provided via `$cliArgs`.
*
* @return void
*/
public function __construct(array $cliArgs=[], $skipSettingStandard=false, $skipSettingReportWidth=false)
{
$this->skipSettingStandard = $skipSettingStandard;
$this->resetSelectProperties();
$this->preventReadingCodeSnifferConfFile();
parent::__construct($cliArgs);
if ($skipSettingReportWidth !== true) {
$this->preventAutoDiscoveryScreenWidth();
}
}//end __construct()
/**
* Ensures the static properties in the Config class are reset to their default values
* when the ConfigDouble is no longer used.
*
* @return void
*/
public function __destruct()
{
$this->setStaticConfigProperty('overriddenDefaults', []);
$this->setStaticConfigProperty('executablePaths', []);
$this->setStaticConfigProperty('configData', null);
$this->setStaticConfigProperty('configDataFile', null);
}//end __destruct()
/**
* Sets the command line values and optionally prevents a file system search for a custom ruleset.
*
* @param array<string> $args An array of command line arguments to set.
*
* @return void
*/
public function setCommandLineValues($args)
{
parent::setCommandLineValues($args);
if ($this->skipSettingStandard !== true) {
$this->preventSearchingForRuleset();
}
}//end setCommandLineValues()
/**
* Reset a few properties on the Config class to their default values.
*
* @return void
*/
private function resetSelectProperties()
{
$this->setStaticConfigProperty('overriddenDefaults', []);
$this->setStaticConfigProperty('executablePaths', []);
}//end resetSelectProperties()
/**
* Prevent the values in a potentially available user-specific `CodeSniffer.conf` file
* from influencing the tests.
*
* This also prevents some file system calls which can influence the test runtime.
*
* @return void
*/
private function preventReadingCodeSnifferConfFile()
{
$this->setStaticConfigProperty('configData', []);
$this->setStaticConfigProperty('configDataFile', '');
}//end preventReadingCodeSnifferConfFile()
/**
* Prevent searching for a custom ruleset by setting a standard, but only if the test
* being run doesn't set a standard itself.
*
* This also prevents some file system calls which can influence the test runtime.
*
* The standard being set is the smallest one available so the ruleset initialization
* will be the fastest possible.
*
* @return void
*/
private function preventSearchingForRuleset()
{
$overriddenDefaults = $this->getStaticConfigProperty('overriddenDefaults');
if (isset($overriddenDefaults['standards']) === false) {
$this->standards = ['PSR1'];
$overriddenDefaults['standards'] = true;
}
self::setStaticConfigProperty('overriddenDefaults', $overriddenDefaults);
}//end preventSearchingForRuleset()
/**
* Prevent a call to stty to figure out the screen width, but only if the test being run
* doesn't set a report width itself.
*
* @return void
*/
private function preventAutoDiscoveryScreenWidth()
{
$settings = $this->getSettings();
if ($settings['reportWidth'] === 'auto') {
$this->reportWidth = self::DEFAULT_REPORT_WIDTH;
}
}//end preventAutoDiscoveryScreenWidth()
/**
* Helper function to retrieve the value of a private static property on the Config class.
*
* @param string $name The name of the property to retrieve.
*
* @return mixed
*/
private function getStaticConfigProperty($name)
{
$property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
(PHP_VERSION_ID < 80100) && $property->setAccessible(true);
return $property->getValue();
}//end getStaticConfigProperty()
/**
* Helper function to set the value of a private static property on the Config class.
*
* @param string $name The name of the property to set.
* @param mixed $value The value to set the property to.
*
* @return void
*/
private function setStaticConfigProperty($name, $value)
{
$property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
(PHP_VERSION_ID < 80100) && $property->setAccessible(true);
$property->setValue(null, $value);
(PHP_VERSION_ID < 80100) && $property->setAccessible(false);
}//end setStaticConfigProperty()
}//end class

View File

@@ -0,0 +1,274 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core;
use Exception;
use PHP_CodeSniffer\Files\DummyFile;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
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 tab width setting to use when tokenizing the file.
*
* This allows for test case files to use a different tab width than the default.
*
* @var integer
*/
protected static $tabWidth = 4;
/**
* 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.
*
* @beforeClass
*
* @return void
*/
public static function initializeFile()
{
$_SERVER['argv'] = [];
$config = new ConfigDouble();
// Also set a tab-width to enable testing tab-replaced vs `orig_content`.
$config->tabWidth = static::$tabWidth;
$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->parse();
}//end initializeFile()
/**
* Clean up after finished test by resetting all static properties on the class to their default values.
*
* Note: This is a PHPUnit cross-version compatible {@see \PHPUnit\Framework\TestCase::tearDownAfterClass()}
* method.
*
* @afterClass
*
* @return void
*/
public static function reset()
{
// Explicitly trigger __destruct() on the ConfigDouble to reset the Config statics.
// The explicit method call prevents potential stray test-local references to the $config object
// preventing the destructor from running the clean up (which without stray references would be
// automagically triggered when `self::$phpcsFile` is reset, but we can't definitively rely on that).
if (isset(self::$phpcsFile) === true) {
self::$phpcsFile->config->__destruct();
}
self::$fileExtension = 'inc';
self::$tabWidth = 4;
self::$phpcsFile = null;
}//end reset()
/**
* Test QA: verify that a test case file does not contain any duplicate test markers.
*
* When a test case file contains a lot of test cases, it is easy to overlook that a test marker name
* is already in use.
* A test wouldn't necessarily fail on this, but would not be testing what is intended to be tested as
* it would be verifying token properties for the wrong token.
*
* This test safeguards against this.
*
* @coversNothing
*
* @return void
*/
public function testTestMarkersAreUnique()
{
$this->assertTestMarkersAreUnique(self::$phpcsFile);
}//end testTestMarkersAreUnique()
/**
* Assertion to verify that a test case file does not contain any duplicate test markers.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file to validate.
*
* @return void
*/
public static function assertTestMarkersAreUnique(File $phpcsFile)
{
$tokens = $phpcsFile->getTokens();
// Collect all marker comments in the file.
$seenComments = [];
for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
if ($tokens[$i]['code'] !== T_COMMENT) {
continue;
}
if (stripos($tokens[$i]['content'], '/* test') !== 0) {
continue;
}
$seenComments[] = $tokens[$i]['content'];
}
self::assertSame(array_unique($seenComments), $seenComments, 'Duplicate test markers found.');
}//end assertTestMarkersAreUnique()
/**
* 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<int|string> $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)
{
return self::getTargetTokenFromFile(self::$phpcsFile, $commentString, $tokenType, $tokenContent);
}//end getTargetToken()
/**
* 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 \PHP_CodeSniffer\Files\File $phpcsFile The file to find the token in.
* @param string $commentString The delimiter comment to look for.
* @param int|string|array<int|string> $tokenType The type of token(s) to look for.
* @param string $tokenContent Optional. The token content for the target token.
*
* @return int
*
* @throws \Exception When the test delimiter comment is not found.
* @throws \Exception When the test target token is not found.
*/
public static function getTargetTokenFromFile(File $phpcsFile, $commentString, $tokenType, $tokenContent=null)
{
$start = ($phpcsFile->numTokens - 1);
$comment = $phpcsFile->findPrevious(
T_COMMENT,
$start,
null,
false,
$commentString
);
if ($comment === false) {
throw new Exception(
sprintf('Failed to find the test marker: %s in test case file %s', $commentString, $phpcsFile->getFilename())
);
}
$tokens = $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 = $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;
}
throw new Exception($msg);
}
return $target;
}//end getTargetTokenFromFile()
/**
* Helper method to tell PHPUnit to expect a PHPCS RuntimeException in a PHPUnit cross-version
* compatible manner.
*
* @param string $message The expected exception message.
*
* @return void
*/
public function expectRunTimeException($message)
{
$exception = 'PHP_CodeSniffer\Exceptions\RuntimeException';
if (method_exists($this, 'expectException') === true) {
// PHPUnit 5+.
$this->expectException($exception);
$this->expectExceptionMessage($message);
} else {
// PHPUnit 4.
$this->setExpectedException($exception, $message);
}
}//end expectRunTimeException()
}//end class

View File

@@ -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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core;
use PHP_CodeSniffer\Tests\FileList;
use PHPUnit\Framework\TestSuite;
use PHPUnit\TextUI\TestRunner;
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

View File

@@ -0,0 +1,126 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Autoload::determineLoadedClass method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
use PHP_CodeSniffer\Autoload;
use PHPUnit\Framework\TestCase;
/**
* Tests for the \PHP_CodeSniffer\Autoload::determineLoadedClass method.
*
* @covers \PHP_CodeSniffer\Autoload::determineLoadedClass
*/
final class DetermineLoadedClassTest extends TestCase
{
/**
* Load the test files.
*
* @beforeClass
*
* @return void
*/
public static function includeFixture()
{
include __DIR__.'/TestFiles/Sub/C.inc';
}//end includeFixture()
/**
* Test for when class list is ordered.
*
* @return void
*/
public function testOrdered()
{
$classesBeforeLoad = [
'classes' => [],
'interfaces' => [],
'traits' => [],
];
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertSame('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testOrdered()
/**
* Test for when class list is out of order.
*
* @return void
*/
public function testUnordered()
{
$classesBeforeLoad = [
'classes' => [],
'interfaces' => [],
'traits' => [],
];
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertSame('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertSame('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
$classesAfterLoad = [
'classes' => [
'PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\A',
'PHP_CodeSniffer\Tests\Core\Autoloader\C',
'PHP_CodeSniffer\Tests\Core\Autoloader\B',
],
'interfaces' => [],
'traits' => [],
];
$className = Autoload::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
$this->assertSame('PHP_CodeSniffer\Tests\Core\Autoloader\Sub\C', $className);
}//end testUnordered()
}//end class

View File

@@ -0,0 +1,3 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
class A {}

View File

@@ -0,0 +1,4 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
require 'A.inc';
class B extends A {}

View File

@@ -0,0 +1,4 @@
<?php
namespace PHP_CodeSniffer\Tests\Core\Autoloader;
require 'B.inc';
class C extends B {}

View File

@@ -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 {}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Test case with helper methods for tests which need to use the *real* Config class (instead of the ConfigDouble).
*
* This test case should be used sparingly and only when it cannot be avoided.
*
* @copyright 2025 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
abstract class AbstractRealConfigTestCase extends TestCase
{
/**
* Set static properties in the Config class to prevent tests influencing each other.
*
* @before
*
* @return void
*/
protected function setConfigStatics()
{
// Set to the property's default value to clear out potentially set values from other tests.
self::setStaticConfigProperty('overriddenDefaults', []);
self::setStaticConfigProperty('executablePaths', []);
// Set to values which prevent the test-runner user's `CodeSniffer.conf` file
// from being read and influencing the tests.
self::setStaticConfigProperty('configData', []);
self::setStaticConfigProperty('configDataFile', '');
}//end setConfigStatics()
/**
* Clean up after each finished test.
*
* @after
*
* @return void
*/
protected function clearArgv()
{
$_SERVER['argv'] = [];
}//end clearArgv()
/**
* Reset the static properties in the Config class to their true defaults to prevent this class
* from influencing other tests.
*
* @afterClass
*
* @return void
*/
public static function resetConfigToDefaults()
{
self::setStaticConfigProperty('overriddenDefaults', []);
self::setStaticConfigProperty('executablePaths', []);
self::setStaticConfigProperty('configData', null);
self::setStaticConfigProperty('configDataFile', null);
$_SERVER['argv'] = [];
}//end resetConfigToDefaults()
/**
* Helper function to set a static property on the Config class.
*
* @param string $name The name of the property to set.
* @param mixed $value The value to set the property to.
*
* @return void
*/
protected static function setStaticConfigProperty($name, $value)
{
$property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
(PHP_VERSION_ID < 80100) && $property->setAccessible(true);
$property->setValue(null, $value);
(PHP_VERSION_ID < 80100) && $property->setAccessible(false);
}//end setStaticConfigProperty()
}//end class

View File

@@ -0,0 +1,128 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Config --extensions argument.
*
* @copyright 2025 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for the \PHP_CodeSniffer\Config --extensions argument.
*
* @covers \PHP_CodeSniffer\Config::processLongArgument
*/
final class ExtensionsArgTest extends TestCase
{
/**
* Ensure that the extension property is set when the parameter is passed a valid value.
*
* @param string $passedValue Extensions as passed on the command line.
* @param string $expected Expected value for the extensions property.
*
* @dataProvider dataValidExtensions
*
* @return void
*/
public function testValidExtensions($passedValue, $expected)
{
$config = new ConfigDouble(["--extensions=$passedValue"]);
$this->assertSame($expected, $config->extensions);
}//end testValidExtensions()
/**
* Data provider.
*
* @see self::testValidExtensions()
*
* @return array<string, array<string, string|array<string, string>>>
*/
public static function dataValidExtensions()
{
return [
// Passing an empty extensions list is not useful, as it will result in no files being scanned,
// but that's the responsibility of the user.
'Empty extensions list' => [
'passedValue' => '',
'expected' => [],
],
'Single extension passed: php' => [
'passedValue' => 'php',
'expected' => [
'php' => 'PHP',
],
],
// This would cause PHPCS to scan python files as PHP, which will probably cause very weird scan results,
// but that's the responsibility of the user.
'Single extension passed: py' => [
'passedValue' => 'py',
'expected' => [
'py' => 'PHP',
],
],
// This would likely result in a problem when PHPCS can't find a "PY" tokenizer class,
// but that's not our concern at this moment. Support for non-PHP tokenizers is being dropped soon anyway.
'Single extension passed with language: py/py' => [
'passedValue' => 'py/py',
'expected' => [
'py' => 'PY',
],
],
'Multiple extensions passed: php,js,css' => [
'passedValue' => 'php,js,css',
'expected' => [
'php' => 'PHP',
'js' => 'JS',
'css' => 'CSS',
],
],
'Multiple extensions passed, some with language: php,inc/php,phpt/php,js' => [
'passedValue' => 'php,inc/php,phpt/php,js',
'expected' => [
'php' => 'PHP',
'inc' => 'PHP',
'phpt' => 'PHP',
'js' => 'JS',
],
],
'File extensions are set case sensitively (and filtering is case sensitive too)' => [
'passedValue' => 'PHP,php',
'expected' => [
'PHP' => 'PHP',
'php' => 'PHP',
],
],
];
}//end dataValidExtensions()
/**
* Ensure that only the first argument is processed and others are ignored.
*
* @return void
*/
public function testOnlySetOnce()
{
$config = new ConfigDouble(
[
'--extensions=php',
'--extensions=inc,module',
]
);
$this->assertSame(['php' => 'PHP'], $config->extensions);
}//end testOnlySetOnce()
}//end class

View File

@@ -0,0 +1,163 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Config --generator argument.
*
* @copyright 2024 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for the \PHP_CodeSniffer\Config --generator argument.
*
* @covers \PHP_CodeSniffer\Config::processLongArgument
*/
final class GeneratorArgTest extends TestCase
{
/**
* Skip these tests when in CBF mode.
*
* @before
*
* @return void
*/
protected function maybeSkipTests()
{
if (PHP_CODESNIFFER_CBF === true) {
$this->markTestSkipped('The `--generator` CLI flag is only supported for the `phpcs` command');
}
}//end maybeSkipTests()
/**
* Ensure that the generator property is set when the parameter is passed a valid value.
*
* @param string $argumentValue Generator name passed on the command line.
* @param string $expectedPropertyValue Expected value of the generator property.
*
* @dataProvider dataValidGeneratorNames
*
* @return void
*/
public function testValidGenerators($argumentValue, $expectedPropertyValue)
{
$config = new ConfigDouble(["--generator=$argumentValue"]);
$this->assertSame($expectedPropertyValue, $config->generator);
}//end testValidGenerators()
/**
* Data provider for testValidGenerators().
*
* @see self::testValidGenerators()
*
* @return array<string, array<string, string>>
*/
public static function dataValidGeneratorNames()
{
return [
'Text generator passed' => [
'argumentValue' => 'Text',
'expectedPropertyValue' => 'Text',
],
'HTML generator passed' => [
'argumentValue' => 'HTML',
'expectedPropertyValue' => 'HTML',
],
'Markdown generator passed' => [
'argumentValue' => 'Markdown',
'expectedPropertyValue' => 'Markdown',
],
'Uppercase Text generator passed' => [
'argumentValue' => 'TEXT',
'expectedPropertyValue' => 'Text',
],
'Mixed case Text generator passed' => [
'argumentValue' => 'tEXt',
'expectedPropertyValue' => 'Text',
],
'Lowercase HTML generator passed' => [
'argumentValue' => 'html',
'expectedPropertyValue' => 'HTML',
],
];
}//end dataValidGeneratorNames()
/**
* Ensure that only the first argument is processed and others are ignored.
*
* @return void
*/
public function testOnlySetOnce()
{
$config = new ConfigDouble(
[
'--generator=Text',
'--generator=HTML',
'--generator=InvalidGenerator',
]
);
$this->assertSame('Text', $config->generator);
}//end testOnlySetOnce()
/**
* Ensure that an exception is thrown for an invalid generator.
*
* @param string $generatorName Generator name.
*
* @dataProvider dataInvalidGeneratorNames
*
* @return void
*/
public function testInvalidGenerator($generatorName)
{
$exception = 'PHP_CodeSniffer\Exceptions\DeepExitException';
$message = 'ERROR: "'.$generatorName.'" is not a valid generator. The following generators are supported: Text, HTML and Markdown.';
if (method_exists($this, 'expectException') === true) {
// PHPUnit 5+.
$this->expectException($exception);
$this->expectExceptionMessage($message);
} else {
// PHPUnit 4.
$this->setExpectedException($exception, $message);
}
new ConfigDouble(["--generator={$generatorName}"]);
}//end testInvalidGenerator()
/**
* Data provider for testInvalidGenerator().
*
* @see self::testInvalidGenerator()
*
* @return array<array<string>>
*/
public static function dataInvalidGeneratorNames()
{
return [
['InvalidGenerator'],
['Text,HTML'],
[''],
];
}//end dataInvalidGeneratorNames()
}//end class

View File

@@ -0,0 +1,64 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Config --report, --report-file and --report-* arguments.
*
* @copyright 2025 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for the \PHP_CodeSniffer\Config --report, --report-file and --report-* arguments.
*
* @covers \PHP_CodeSniffer\Config::processLongArgument
*/
final class ReportArgsTest extends TestCase
{
/**
* [CS mode] Verify that passing `--report-file` does not influence *which* reports get activated.
*
* @return void
*/
public function testReportFileDoesNotSetReportsCs()
{
if (PHP_CODESNIFFER_CBF === true) {
$this->markTestSkipped('This test needs CS mode to run');
}
$config = new ConfigDouble(['--report-file='.__DIR__.'/report.txt']);
$this->assertTrue(is_string($config->reportFile));
$this->assertStringEndsWith('/report.txt', $config->reportFile);
$this->assertSame(['full' => null], $config->reports);
}//end testReportFileDoesNotSetReportsCs()
/**
* [CBF mode] Verify that passing `--report-file` does not influence *which* reports get activated.
*
* @group CBF
*
* @return void
*/
public function testReportFileDoesNotSetReportsCbf()
{
if (PHP_CODESNIFFER_CBF === false) {
$this->markTestSkipped('This test needs CBF mode to run');
}
$config = new ConfigDouble(['--report-file='.__DIR__.'/report.txt']);
$this->assertNull($config->reportFile);
$this->assertSame(['full' => null], $config->reports);
}//end testReportFileDoesNotSetReportsCbf()
}//end class

View File

@@ -0,0 +1,260 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Config reportWidth value.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2006-2023 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Tests\Core\Config\AbstractRealConfigTestCase;
/**
* Tests for the \PHP_CodeSniffer\Config reportWidth value.
*
* @covers \PHP_CodeSniffer\Config::__get
*/
final class ReportWidthTest extends AbstractRealConfigTestCase
{
/**
* Test that report width without overrules will always be set to a non-0 positive integer.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::restoreDefaults
*
* @return void
*/
public function testReportWidthDefault()
{
$config = new Config(['--standard=PSR1']);
// Can't test the exact value as "auto" will resolve differently depending on the machine running the tests.
$this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer');
$this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0');
}//end testReportWidthDefault()
/**
* Test that the report width will be set to a non-0 positive integer when not found in the CodeSniffer.conf file.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::restoreDefaults
*
* @return void
*/
public function testReportWidthWillBeSetFromAutoWhenNotFoundInConfFile()
{
$phpCodeSnifferConfig = [
'default_standard' => 'PSR2',
'show_warnings' => '0',
];
$this->setStaticConfigProperty('configData', $phpCodeSnifferConfig);
$config = new Config(['--standard=PSR1']);
// Can't test the exact value as "auto" will resolve differently depending on the machine running the tests.
$this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer');
$this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0');
}//end testReportWidthWillBeSetFromAutoWhenNotFoundInConfFile()
/**
* Test that the report width will be set correctly when found in the CodeSniffer.conf file.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::getConfigData
* @covers \PHP_CodeSniffer\Config::restoreDefaults
*
* @return void
*/
public function testReportWidthCanBeSetFromConfFile()
{
$phpCodeSnifferConfig = [
'default_standard' => 'PSR2',
'report_width' => '120',
];
$this->setStaticConfigProperty('configData', $phpCodeSnifferConfig);
$config = new Config(['--standard=PSR1']);
$this->assertSame(120, $config->reportWidth);
}//end testReportWidthCanBeSetFromConfFile()
/**
* Test that the report width will be set correctly when passed as a CLI argument.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::processLongArgument
*
* @return void
*/
public function testReportWidthCanBeSetFromCLI()
{
$_SERVER['argv'] = [
'phpcs',
'--standard=PSR1',
'--report-width=100',
];
$config = new Config();
$this->assertSame(100, $config->reportWidth);
}//end testReportWidthCanBeSetFromCLI()
/**
* Test that the report width will be set correctly when multiple report widths are passed on the CLI.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::processLongArgument
*
* @return void
*/
public function testReportWidthWhenSetFromCLIFirstValuePrevails()
{
$_SERVER['argv'] = [
'phpcs',
'--standard=PSR1',
'--report-width=100',
'--report-width=200',
];
$config = new Config();
$this->assertSame(100, $config->reportWidth);
}//end testReportWidthWhenSetFromCLIFirstValuePrevails()
/**
* Test that a report width passed as a CLI argument will overrule a report width set in a CodeSniffer.conf file.
*
* @covers \PHP_CodeSniffer\Config::__set
* @covers \PHP_CodeSniffer\Config::processLongArgument
* @covers \PHP_CodeSniffer\Config::getConfigData
*
* @return void
*/
public function testReportWidthSetFromCLIOverrulesConfFile()
{
$phpCodeSnifferConfig = [
'default_standard' => 'PSR2',
'report_format' => 'summary',
'show_warnings' => '0',
'show_progress' => '1',
'report_width' => '120',
];
$this->setStaticConfigProperty('configData', $phpCodeSnifferConfig);
$cliArgs = [
'phpcs',
'--standard=PSR1',
'--report-width=180',
];
$config = new Config($cliArgs);
$this->assertSame(180, $config->reportWidth);
}//end testReportWidthSetFromCLIOverrulesConfFile()
/**
* Test that the report width will be set to a non-0 positive integer when set to "auto".
*
* @covers \PHP_CodeSniffer\Config::__set
*
* @return void
*/
public function testReportWidthInputHandlingForAuto()
{
$config = new Config(['--standard=PSR1']);
$config->reportWidth = 'auto';
// Can't test the exact value as "auto" will resolve differently depending on the machine running the tests.
$this->assertTrue(is_int($config->reportWidth), 'Report width is not an integer');
$this->assertGreaterThan(0, $config->reportWidth, 'Report width is not greater than 0');
}//end testReportWidthInputHandlingForAuto()
/**
* Test that the report width will be set correctly for various types of input.
*
* @param mixed $value Input value received.
* @param int $expected Expected report width.
*
* @dataProvider dataReportWidthInputHandling
* @covers \PHP_CodeSniffer\Config::__set
*
* @return void
*/
public function testReportWidthInputHandling($value, $expected)
{
$config = new Config(['--standard=PSR1']);
$config->reportWidth = $value;
$this->assertSame($expected, $config->reportWidth);
}//end testReportWidthInputHandling()
/**
* Data provider.
*
* @return array<string, array<string, mixed>>
*/
public static function dataReportWidthInputHandling()
{
return [
'No value (empty string)' => [
'value' => '',
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: invalid input type null' => [
'value' => null,
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: invalid input type false' => [
'value' => false,
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: invalid input type float' => [
'value' => 100.50,
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: invalid string value "invalid"' => [
'value' => 'invalid',
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: invalid string value, non-integer string "50.25"' => [
'value' => '50.25',
'expected' => Config::DEFAULT_REPORT_WIDTH,
],
'Value: valid numeric string value' => [
'value' => '250',
'expected' => 250,
],
'Value: valid int value' => [
'value' => 220,
'expected' => 220,
],
'Value: negative int value becomes positive int' => [
'value' => -180,
'expected' => 180,
],
];
}//end dataReportWidthInputHandling()
}//end class

View File

@@ -0,0 +1,327 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Config --sniffs and --exclude arguments.
*
* @author Dan Wallis <dan@wallis.nz>
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Config;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for the \PHP_CodeSniffer\Config --sniffs and --exclude arguments.
*
* @covers \PHP_CodeSniffer\Config::parseSniffCodes
* @covers \PHP_CodeSniffer\Config::processLongArgument
*/
final class SniffsExcludeArgsTest extends TestCase
{
/**
* Ensure that the expected error message is returned for invalid arguments.
*
* @param string $argument 'sniffs' or 'exclude'.
* @param string $value List of sniffs to include / exclude.
* @param array<string> $errors Sniff code and associated help text.
* @param string|null $suggestion Help text shown to end user with correct syntax for argument.
*
* @return void
* @dataProvider dataInvalid
*/
public function testInvalid($argument, $value, $errors, $suggestion)
{
$cmd = 'phpcs';
if (PHP_CODESNIFFER_CBF === true) {
$cmd = 'phpcbf';
}
$exception = 'PHP_CodeSniffer\Exceptions\DeepExitException';
$message = 'ERROR: The --'.$argument.' option only supports sniff codes.'.PHP_EOL;
$message .= 'Sniff codes are in the form "Standard.Category.Sniff".'.PHP_EOL;
$message .= PHP_EOL;
$message .= 'The following problems were detected:'.PHP_EOL;
$message .= '* '.implode(PHP_EOL.'* ', $errors).PHP_EOL;
if ($suggestion !== null) {
$message .= PHP_EOL;
$message .= "Perhaps try --$argument=\"$suggestion\" instead.".PHP_EOL;
}
$message .= PHP_EOL;
$message .= "Run \"{$cmd} --help\" for usage information".PHP_EOL;
$message .= PHP_EOL;
if (method_exists($this, 'expectException') === true) {
// PHPUnit 5+.
$this->expectException($exception);
$this->expectExceptionMessage($message);
} else {
// PHPUnit 4.
$this->setExpectedException($exception, $message);
}
new ConfigDouble(["--$argument=$value"]);
}//end testInvalid()
/**
* Data provider for testInvalid().
*
* @see self::testInvalid()
* @return array<string, array<string, array<string>|string|null>>
*/
public static function dataInvalid()
{
$arguments = [
'sniffs',
'exclude',
];
$data = [];
foreach ($arguments as $argument) {
// Empty values are errors.
$data[$argument.'; empty string'] = [
'argument' => $argument,
'value' => '',
'errors' => [
'No codes specified / empty argument',
],
'suggestion' => null,
];
$data[$argument.'; one comma alone'] = [
'argument' => $argument,
'value' => ',',
'errors' => [
'No codes specified / empty argument',
],
'suggestion' => null,
];
$data[$argument.'; two commas alone'] = [
'argument' => $argument,
'value' => ',,',
'errors' => [
'No codes specified / empty argument',
],
'suggestion' => null,
];
// A standard is not a valid sniff.
$data[$argument.'; standard'] = [
'argument' => $argument,
'value' => 'Standard',
'errors' => [
'Standard codes are not supported: Standard',
],
'suggestion' => null,
];
// A category is not a valid sniff.
$data[$argument.'; category'] = [
'argument' => $argument,
'value' => 'Standard.Category',
'errors' => [
'Category codes are not supported: Standard.Category',
],
'suggestion' => null,
];
// An error-code is not a valid sniff.
$data[$argument.'; error-code'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff.Code',
'errors' => [
'Message codes are not supported: Standard.Category.Sniff.Code',
],
'suggestion' => 'Standard.Category.Sniff',
];
// Too many dots.
$data[$argument.'; too many dots'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff.Code.Extra',
'errors' => [
'Too many parts: Standard.Category.Sniff.Code.Extra',
],
'suggestion' => 'Standard.Category.Sniff',
];
// All errors are reported in one go.
$data[$argument.'; two errors'] = [
'argument' => $argument,
'value' => 'StandardOne,StandardTwo',
'errors' => [
'Standard codes are not supported: StandardOne',
'Standard codes are not supported: StandardTwo',
],
'suggestion' => null,
];
// Order of valid/invalid does not impact error reporting.
$data[$argument.'; valid followed by invalid'] = [
'argument' => $argument,
'value' => 'StandardOne.Category.Sniff,StandardTwo.Category',
'errors' => [
'Category codes are not supported: StandardTwo.Category',
],
'suggestion' => 'StandardOne.Category.Sniff',
];
$data[$argument.'; invalid followed by valid'] = [
'argument' => $argument,
'value' => 'StandardOne.Category,StandardTwo.Category.Sniff',
'errors' => [
'Category codes are not supported: StandardOne.Category',
],
'suggestion' => 'StandardTwo.Category.Sniff',
];
// Different cases are reported individually (in duplicate), but suggestions are reduced.
$data[$argument.'; case mismatch - different errors'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff.Code,sTANDARD.cATEGORY.sNIFF.cODE.eXTRA',
'errors' => [
'Message codes are not supported: Standard.Category.Sniff.Code',
'Too many parts: sTANDARD.cATEGORY.sNIFF.cODE.eXTRA',
],
'suggestion' => 'Standard.Category.Sniff',
];
$data[$argument.'; case mismatch - same error'] = [
'argument' => $argument,
'value' => 'sTANDARD.cATEGORY.sNIFF.cODE,Standard.Category.Sniff.Code',
'errors' => [
'Message codes are not supported: sTANDARD.cATEGORY.sNIFF.cODE',
'Message codes are not supported: Standard.Category.Sniff.Code',
],
'suggestion' => 'sTANDARD.cATEGORY.sNIFF',
];
}//end foreach
return $data;
}//end dataInvalid()
/**
* Ensure that the valid data does not throw an exception, and the value is stored.
*
* @param string $argument 'sniffs' or 'exclude'.
* @param string $value List of sniffs to include or exclude.
* @param array<string> $result Expected sniffs to be set on the Config object.
*
* @return void
* @dataProvider dataValid
*/
public function testValid($argument, $value, $result)
{
$config = new ConfigDouble(["--$argument=$value"]);
$this->assertSame($result, $config->$argument);
}//end testValid()
/**
* Data provider for testValid().
*
* @see self::testValid()
* @return array<string, array<string, array<string>|string>>
*/
public static function dataValid()
{
$arguments = [
'sniffs',
'exclude',
];
$data = [];
foreach ($arguments as $argument) {
$data[$argument.'; one valid sniff'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff',
'result' => ['Standard.Category.Sniff'],
];
$data[$argument.'; two valid sniffs'] = [
'argument' => $argument,
'value' => 'StandardOne.Category.Sniff,StandardTwo.Category.Sniff',
'result' => [
'StandardOne.Category.Sniff',
'StandardTwo.Category.Sniff',
],
];
// Rogue commas are quietly ignored.
$data[$argument.'; trailing comma'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff,',
'result' => ['Standard.Category.Sniff'],
];
$data[$argument.'; double comma between sniffs'] = [
'argument' => $argument,
'value' => 'StandardOne.Category.Sniff,,StandardTwo.Category.Sniff',
'result' => [
'StandardOne.Category.Sniff',
'StandardTwo.Category.Sniff',
],
];
// Duplicates are reduced silently.
$data[$argument.'; one valid sniff twice'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff,Standard.Category.Sniff',
'result' => ['Standard.Category.Sniff'],
];
$data[$argument.'; one valid sniff in different cases'] = [
'argument' => $argument,
'value' => 'Standard.Category.Sniff, standard.category.sniff, STANDARD.CATEGORY.SNIFF',
'result' => ['Standard.Category.Sniff'],
];
}//end foreach
return $data;
}//end dataValid()
/**
* Ensure that only the first argument is processed and others are ignored.
*
* @param string $argument 'sniffs' or 'exclude'.
*
* @return void
* @dataProvider dataOnlySetOnce
*/
public function testOnlySetOnce($argument)
{
$config = new ConfigDouble(
[
"--$argument=StandardOne.Category.Sniff",
"--$argument=StandardTwo.Category.Sniff",
"--$argument=Standard.AnotherCategory.Sniff",
]
);
$this->assertSame(['StandardOne.Category.Sniff'], $config->$argument);
}//end testOnlySetOnce()
/**
* Data provider for testOnlySetOnce().
*
* @return array<string, array<string>>
*/
public static function dataOnlySetOnce()
{
return [
'sniffs' => ['sniffs'],
'exclude' => ['exclude'],
];
}//end dataOnlySetOnce()
}//end class

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
<?php
/* testSimpleAssignment */
$a = false;
/* testControlStructure */
while(true) {}
$a = 1;
/* testClosureAssignment */
$a = function($b=false;){};
/* testHeredocFunctionArg */
myFunction(<<<END
Foo
END
, 'bar');
/* testSwitch */
switch ($a) {
case 1: {break;}
default: {break;}
}
/* testStatementAsArrayValue */
$a = [new Datetime];
$a = array(new Datetime);
$a = new Datetime;
/* testUseGroup */
use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
$a = [
/* testArrowFunctionArrayValue */
'a' => fn() => return 1,
'b' => fn() => return 1,
];
/* testStaticArrowFunction */
static fn ($a) => $a;
return 0;
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
/* testArrowFunctionAsArgument */
$foo = foo(
fn() => bar()
);
/* testArrowFunctionWithArrayAsArgument */
$foo = foo(
fn() => [$row[0], $row[3]]
);
$match = match ($a) {
/* testMatchCase */
1 => 'foo',
/* testMatchDefault */
default => 'bar'
};
$match = match ($a) {
/* testMatchMultipleCase */
1, 2, => $a * $b,
/* testMatchDefaultComma */
default, => 'something'
};
match ($pressedKey) {
/* testMatchFunctionCall */
Key::RETURN_ => save($value, $user)
};
$result = match (true) {
/* testMatchFunctionCallArm */
str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
default => 'pl'
};
/* testMatchClosure */
$result = match ($key) {
1 => function($a, $b) {},
2 => function($b, $c) {},
};
/* testMatchArray */
$result = match ($key) {
1 => [1,2,3],
2 => [1 => one(), 2 => two()],
};
/* testNestedMatch */
$result = match ($key) {
1 => match ($key) {
1 => 'one',
2 => 'two',
},
2 => match ($key) {
1 => 'two',
2 => 'one',
},
};

View File

@@ -0,0 +1,457 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
use PHP_CodeSniffer\Util\Tokens;
/**
* Tests for the \PHP_CodeSniffer\Files\File::findEndOfStatement method.
*
* @covers \PHP_CodeSniffer\Files\File::findEndOfStatement
*/
final class FindEndOfStatementTest extends AbstractMethodUnitTest
{
/**
* Test that end of statement is NEVER before the "current" token.
*
* @return void
*/
public function testEndIsNeverLessThanCurrentToken()
{
$tokens = self::$phpcsFile->getTokens();
$errors = [];
for ($i = 0; $i < self::$phpcsFile->numTokens; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
continue;
}
$end = self::$phpcsFile->findEndOfStatement($i);
// Collect all the errors.
if ($end < $i) {
$errors[] = sprintf(
'End of statement for token %1$d (%2$s: %3$s) on line %4$d is %5$d (%6$s), which is less than %1$d',
$i,
$tokens[$i]['type'],
$tokens[$i]['content'],
$tokens[$i]['line'],
$end,
$tokens[$end]['type']
);
}
}
$this->assertSame([], $errors);
}//end testEndIsNeverLessThanCurrentToken()
/**
* Test a simple assignment.
*
* @return void
*/
public function testSimpleAssignment()
{
$start = $this->getTargetToken('/* testSimpleAssignment */', T_VARIABLE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testSimpleAssignment()
/**
* Test a direct call to a control structure.
*
* @return void
*/
public function testControlStructure()
{
$start = $this->getTargetToken('/* testControlStructure */', T_WHILE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 6), $found);
}//end testControlStructure()
/**
* Test the assignment of a closure.
*
* @return void
*/
public function testClosureAssignment()
{
$start = $this->getTargetToken('/* testClosureAssignment */', T_VARIABLE, '$a');
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 13), $found);
}//end testClosureAssignment()
/**
* Test using a heredoc in a function argument.
*
* @return void
*/
public function testHeredocFunctionArg()
{
// Find the end of the function.
$start = $this->getTargetToken('/* testHeredocFunctionArg */', T_STRING, 'myFunction');
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 10), $found);
// Find the end of the heredoc.
$start += 2;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 4), $found);
// Find the end of the last arg.
$start = ($found + 2);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame($start, $found);
}//end testHeredocFunctionArg()
/**
* Test parts of a switch statement.
*
* @return void
*/
public function testSwitch()
{
// Find the end of the switch.
$start = $this->getTargetToken('/* testSwitch */', T_SWITCH);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 28), $found);
// Find the end of the case.
$start += 9;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 8), $found);
// Find the end of default case.
$start += 11;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 6), $found);
}//end testSwitch()
/**
* Test statements that are array values.
*
* @return void
*/
public function testStatementAsArrayValue()
{
// Test short array syntax.
$start = $this->getTargetToken('/* testStatementAsArrayValue */', T_NEW);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 2), $found);
// Test long array syntax.
$start += 12;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 2), $found);
// Test same statement outside of array.
$start += 10;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 3), $found);
}//end testStatementAsArrayValue()
/**
* Test a use group.
*
* @return void
*/
public function testUseGroup()
{
$start = $this->getTargetToken('/* testUseGroup */', T_USE);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 23), $found);
}//end testUseGroup()
/**
* Test arrow function as array value.
*
* @return void
*/
public function testArrowFunctionArrayValue()
{
$start = $this->getTargetToken('/* testArrowFunctionArrayValue */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 9), $found);
}//end testArrowFunctionArrayValue()
/**
* Test static arrow function.
*
* @return void
*/
public function testStaticArrowFunction()
{
$static = $this->getTargetToken('/* testStaticArrowFunction */', T_STATIC);
$fn = $this->getTargetToken('/* testStaticArrowFunction */', T_FN);
$endOfStatementStatic = self::$phpcsFile->findEndOfStatement($static);
$endOfStatementFn = self::$phpcsFile->findEndOfStatement($fn);
$this->assertSame($endOfStatementFn, $endOfStatementStatic);
}//end testStaticArrowFunction()
/**
* Test arrow function with return value.
*
* @return void
*/
public function testArrowFunctionReturnValue()
{
$start = $this->getTargetToken('/* testArrowFunctionReturnValue */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 18), $found);
}//end testArrowFunctionReturnValue()
/**
* Test arrow function used as a function argument.
*
* @return void
*/
public function testArrowFunctionAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionAsArgument */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 8), $found);
}//end testArrowFunctionAsArgument()
/**
* Test arrow function with arrays used as a function argument.
*
* @return void
*/
public function testArrowFunctionWithArrayAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionWithArrayAsArgument */', T_FN);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 17), $found);
}//end testArrowFunctionWithArrayAsArgument()
/**
* Test simple match expression case.
*
* @return void
*/
public function testMatchCase()
{
$start = $this->getTargetToken('/* testMatchCase */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
$start = $this->getTargetToken('/* testMatchCase */', T_CONSTANT_ENCAPSED_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 1), $found);
}//end testMatchCase()
/**
* Test simple match expression default case.
*
* @return void
*/
public function testMatchDefault()
{
$start = $this->getTargetToken('/* testMatchDefault */', T_MATCH_DEFAULT);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 4), $found);
$start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefault()
/**
* Test multiple comma-separated match expression case values.
*
* @return void
*/
public function testMatchMultipleCase()
{
$start = $this->getTargetToken('/* testMatchMultipleCase */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 13), $found);
$start += 6;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 7), $found);
}//end testMatchMultipleCase()
/**
* Test match expression default case with trailing comma.
*
* @return void
*/
public function testMatchDefaultComma()
{
$start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_DEFAULT);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testMatchDefaultComma()
/**
* Test match expression with function call.
*
* @return void
*/
public function testMatchFunctionCall()
{
$start = $this->getTargetToken('/* testMatchFunctionCall */', T_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 12), $found);
$start += 8;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 1), $found);
}//end testMatchFunctionCall()
/**
* Test match expression with function call in the arm.
*
* @return void
*/
public function testMatchFunctionCallArm()
{
// Check the first case.
$start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_STRING);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 21), $found);
// Check the second case.
$start += 24;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 21), $found);
}//end testMatchFunctionCallArm()
/**
* Test match expression with closure.
*
* @return void
*/
public function testMatchClosure()
{
$start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 14), $found);
$start += 17;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 14), $found);
}//end testMatchClosure()
/**
* Test match expression with array declaration.
*
* @return void
*/
public function testMatchArray()
{
$start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 11), $found);
$start += 14;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 22), $found);
}//end testMatchArray()
/**
* Test nested match expressions.
*
* @return void
*/
public function testNestedMatch()
{
$start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER);
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 30), $found);
$start += 21;
$found = self::$phpcsFile->findEndOfStatement($start);
$this->assertSame(($start + 5), $found);
}//end testNestedMatch()
}//end class

View File

@@ -0,0 +1,58 @@
<?php
/* testNotAClass */
function notAClass() {}
/* testNonExtendedClass */
class testFECNNonExtendedClass {}
/* testExtendsUnqualifiedClass */
class testFECNExtendedClass extends testFECNClass {}
/* testExtendsFullyQualifiedClass */
class testFECNNamespacedClass extends \PHP_CodeSniffer\Tests\Core\File\testFECNClass {}
/* testExtendsPartiallyQualifiedClass */
class testFECNQualifiedClass extends Core\File\RelativeClass {}
/* testNonExtendedInterface */
interface testFECNInterface {}
/* testInterfaceExtendsUnqualifiedInterface */
interface testInterfaceThatExtendsInterface extends testFECNInterface{}
/* testInterfaceExtendsFullyQualifiedInterface */
interface testInterfaceThatExtendsFQCNInterface extends \PHP_CodeSniffer\Tests\Core\File\testFECNInterface{}
/* testExtendedAnonClass */
$anon = new class( $a, $b ) extends testFECNExtendedAnonClass {};
/* 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 {}
/* testInterfaceMultiExtends */
interface Multi extends \Package\FooInterface, \BarInterface {};
/* testExtendedReadonlyAnonClass */
$anon = new readonly class() extends \Fully\Qualified\MyClass {};
/* testExtendedAnonClassWithAttributes */
$anon = new #[SomeAttribute] class( $p ) extends Partially\Qualified\MyClass {};
/* testMissingExtendsName */
class testMissingExtendsName extends { /* missing classname */ } // Intentional parse error.
// Intentional parse error. Has to be the last test in the file.
/* testParseError */
class testParseError extends testFECNClass

View File

@@ -0,0 +1,153 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File::findExtendedClassName method.
*
* @covers \PHP_CodeSniffer\Files\File::findExtendedClassName
*/
final class FindExtendedClassNameTest extends AbstractMethodUnitTest
{
/**
* Test getting a `false` result when a non-existent token is passed.
*
* @return void
*/
public function testNonExistentToken()
{
$result = self::$phpcsFile->findExtendedClassName(100000);
$this->assertFalse($result);
}//end testNonExistentToken()
/**
* Test getting a `false` result when a token other than one of the supported tokens is passed.
*
* @return void
*/
public function testNotAClass()
{
$token = $this->getTargetToken('/* testNotAClass */', [T_FUNCTION]);
$result = self::$phpcsFile->findExtendedClassName($token);
$this->assertFalse($result);
}//end testNotAClass()
/**
* Test retrieving the name of the class being extended by another class
* (or interface).
*
* @param string $identifier Comment which precedes the test case.
* @param string|false $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<string, array<string, string|false>>
*/
public static function dataExtendedClass()
{
return [
'class does not extend' => [
'identifier' => '/* testNonExtendedClass */',
'expected' => false,
],
'class extends unqualified class' => [
'identifier' => '/* testExtendsUnqualifiedClass */',
'expected' => 'testFECNClass',
],
'class extends fully qualified class' => [
'identifier' => '/* testExtendsFullyQualifiedClass */',
'expected' => '\PHP_CodeSniffer\Tests\Core\File\testFECNClass',
],
'class extends partially qualified class' => [
'identifier' => '/* testExtendsPartiallyQualifiedClass */',
'expected' => 'Core\File\RelativeClass',
],
'interface does not extend' => [
'identifier' => '/* testNonExtendedInterface */',
'expected' => false,
],
'interface extends unqualified interface' => [
'identifier' => '/* testInterfaceExtendsUnqualifiedInterface */',
'expected' => 'testFECNInterface',
],
'interface extends fully qualified interface' => [
'identifier' => '/* testInterfaceExtendsFullyQualifiedInterface */',
'expected' => '\PHP_CodeSniffer\Tests\Core\File\testFECNInterface',
],
'anon class extends unqualified class' => [
'identifier' => '/* testExtendedAnonClass */',
'expected' => 'testFECNExtendedAnonClass',
],
'class does not extend but contains anon class which extends' => [
'identifier' => '/* testNestedExtendedClass */',
'expected' => false,
],
'anon class extends, nested in non-extended class' => [
'identifier' => '/* testNestedExtendedAnonClass */',
'expected' => 'testFECNAnonClass',
],
'class extends and implements' => [
'identifier' => '/* testClassThatExtendsAndImplements */',
'expected' => 'testFECNClass',
],
'class implements and extends' => [
'identifier' => '/* testClassThatImplementsAndExtends */',
'expected' => 'testFECNClass',
],
'interface extends multiple interfaces (not supported)' => [
'identifier' => '/* testInterfaceMultiExtends */',
'expected' => '\Package\FooInterface',
],
'readonly anon class extends fully qualified class' => [
'identifier' => '/* testExtendedReadonlyAnonClass */',
'expected' => '\Fully\Qualified\MyClass',
],
'anon class with attribute extends partially qualified class' => [
'identifier' => '/* testExtendedAnonClassWithAttributes */',
'expected' => 'Partially\Qualified\MyClass',
],
'parse error - extends keyword, but no class name' => [
'identifier' => '/* testMissingExtendsName */',
'expected' => false,
],
'parse error - live coding - no curly braces' => [
'identifier' => '/* testParseError */',
'expected' => false,
],
];
}//end dataExtendedClass()
}//end class

View File

@@ -0,0 +1,47 @@
<?php
/* testNotAClass */
function notAClass() {}
/* testPlainInterface */
interface testFIINInterface {}
/* testNonImplementedClass */
class testFIINNonImplementedClass {}
/* testClassImplementsSingle */
class testFIINImplementedClass implements testFIINInterface {}
/* testClassImplementsMultiple */
class testFIINMultiImplementedClass implements testFIINInterface, testFIINInterface2 {}
/* testImplementsFullyQualified */
class testFIINNamespacedClass implements \PHP_CodeSniffer\Tests\Core\File\testFIINInterface {}
/* testImplementsPartiallyQualified */
class testFIINQualifiedClass implements Core\File\RelativeInterface {}
/* testClassThatExtendsAndImplements */
class testFECNClassThatExtendsAndImplements extends testFECNClass implements InterfaceA, \NameSpaced\Cat\InterfaceB {}
/* testClassThatImplementsAndExtends */
class testFECNClassThatImplementsAndExtends implements \InterfaceA, InterfaceB extends testFECNClass {}
/* testBackedEnumWithoutImplements */
enum Suit:string {}
/* testEnumImplementsSingle */
enum Suit implements Colorful {}
/* testBackedEnumImplementsMulti */
enum Suit: string implements Colorful, \Deck {}
/* testAnonClassImplementsSingle */
$anon = class() implements testFIINInterface {}
/* testMissingImplementsName */
class testMissingExtendsName implements { /* missing interface name */ } // Intentional parse error.
// Intentional parse error. Has to be the last test in the file.
/* testParseError */
class testParseError implements testInterface

View File

@@ -0,0 +1,162 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File::findImplementedInterfaceNames method.
*
* @covers \PHP_CodeSniffer\Files\File::findImplementedInterfaceNames
*/
final class FindImplementedInterfaceNamesTest extends AbstractMethodUnitTest
{
/**
* Test getting a `false` result when a non-existent token is passed.
*
* @return void
*/
public function testNonExistentToken()
{
$result = self::$phpcsFile->findImplementedInterfaceNames(100000);
$this->assertFalse($result);
}//end testNonExistentToken()
/**
* Test getting a `false` result when a token other than one of the supported tokens is passed.
*
* @return void
*/
public function testNotAClass()
{
$token = $this->getTargetToken('/* testNotAClass */', [T_FUNCTION]);
$result = self::$phpcsFile->findImplementedInterfaceNames($token);
$this->assertFalse($result);
}//end testNotAClass()
/**
* Test retrieving the name(s) of the interfaces being implemented by a class.
*
* @param string $identifier Comment which precedes the test case.
* @param array<string>|false $expected Expected function output.
*
* @dataProvider dataImplementedInterface
*
* @return void
*/
public function testFindImplementedInterfaceNames($identifier, $expected)
{
$OOToken = $this->getTargetToken($identifier, [T_CLASS, T_ANON_CLASS, T_INTERFACE, T_ENUM]);
$result = self::$phpcsFile->findImplementedInterfaceNames($OOToken);
$this->assertSame($expected, $result);
}//end testFindImplementedInterfaceNames()
/**
* Data provider for the FindImplementedInterfaceNames test.
*
* @see testFindImplementedInterfaceNames()
*
* @return array<string, array<string, string|array<string>|false>>
*/
public static function dataImplementedInterface()
{
return [
'interface declaration, no implements' => [
'identifier' => '/* testPlainInterface */',
'expected' => false,
],
'class does not implement' => [
'identifier' => '/* testNonImplementedClass */',
'expected' => false,
],
'class implements single interface, unqualified' => [
'identifier' => '/* testClassImplementsSingle */',
'expected' => [
'testFIINInterface',
],
],
'class implements multiple interfaces' => [
'identifier' => '/* testClassImplementsMultiple */',
'expected' => [
'testFIINInterface',
'testFIINInterface2',
],
],
'class implements single interface, fully qualified' => [
'identifier' => '/* testImplementsFullyQualified */',
'expected' => [
'\PHP_CodeSniffer\Tests\Core\File\testFIINInterface',
],
],
'class implements single interface, partially qualified' => [
'identifier' => '/* testImplementsPartiallyQualified */',
'expected' => [
'Core\File\RelativeInterface',
],
],
'class extends and implements' => [
'identifier' => '/* testClassThatExtendsAndImplements */',
'expected' => [
'InterfaceA',
'\NameSpaced\Cat\InterfaceB',
],
],
'class implements and extends' => [
'identifier' => '/* testClassThatImplementsAndExtends */',
'expected' => [
'\InterfaceA',
'InterfaceB',
],
],
'enum does not implement' => [
'identifier' => '/* testBackedEnumWithoutImplements */',
'expected' => false,
],
'enum implements single interface, unqualified' => [
'identifier' => '/* testEnumImplementsSingle */',
'expected' => [
'Colorful',
],
],
'enum implements multiple interfaces, unqualified + fully qualified' => [
'identifier' => '/* testBackedEnumImplementsMulti */',
'expected' => [
'Colorful',
'\Deck',
],
],
'anon class implements single interface, unqualified' => [
'identifier' => '/* testAnonClassImplementsSingle */',
'expected' => [
'testFIINInterface',
],
],
'parse error - implements keyword, but no interface name' => [
'identifier' => '/* testMissingImplementsName */',
'expected' => false,
],
'parse error - live coding - no curly braces' => [
'identifier' => '/* testParseError */',
'expected' => false,
],
];
}//end dataImplementedInterface()
}//end class

View File

@@ -0,0 +1,212 @@
<?php
/* testSimpleAssignment */
$a = false;
/* testFunctionCall */
$a = doSomething();
/* testFunctionCallArgument */
$a = doSomething($a, $b);
/* testControlStructure */
while(true) {}
$a = 1;
/* testClosureAssignment */
$a = function($b=false){};
/* testHeredocFunctionArg */
myFunction(<<<END
Foo
END
, 'bar');
switch ($a) {
case 1: {break;}
case 2: $foo = true; break;
default: {break;}
/* testSwitch */
}
/* testStatementAsArrayValue */
$a = [new Datetime];
$a = array(new Datetime);
$a = ['a' => $foo + $bar, 'b' => true];
/* testUseGroup */
use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
$a = [
/* testArrowFunctionArrayValue */
'a' => fn() => 1,
'b' => fn() => 1,
];
/* testStaticArrowFunction */
static fn ($a) => $a;
/* testArrowFunctionReturnValue */
fn(): array => [a($a, $b)];
/* testArrowFunctionAsArgument */
$foo = foo(
fn() => bar()
);
/* testArrowFunctionWithArrayAsArgument */
$foo = foo(
fn() => [$row[0], $row[3]]
);
$match = match ($a) {
/* testMatchCase */
1 => 'foo',
/* testMatchDefault */
default => 'bar'
};
$match = match ($a) {
/* testMatchMultipleCase */
1, 2, => $a * $b,
/* testMatchDefaultComma */
default, => 'something'
};
match ($pressedKey) {
/* testMatchFunctionCall */
Key::RETURN_ => save($value, $user)
};
$result = match (true) {
/* testMatchFunctionCallArm */
str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
default => 'pl'
};
/* testMatchClosure */
$result = match ($key) {
1 => function($a, $b) {},
2 => function($b, $c) {},
};
/* testMatchArray */
$result = match ($key) {
1 => [1,2,3],
2 => [1 => one($a, $b), 2 => two($b, $c)],
3 => [],
};
/* testNestedMatch */
$result = match ($key) {
1 => match ($key) {
1 => 'one',
2 => 'two',
},
2 => match ($key) {
1 => 'two',
2 => 'one',
},
};
return 0;
/* testOpenTag */
?>
<h1>Test</h1>
<?php echo '<h2>', foo(), '</h2>';
/* testOpenTagWithEcho */
?>
<h1>Test</h1>
<?= '<h2>', foo(), '</h2>';
$value = [
/* testPrecededByArrowFunctionInArray - Expected */
Url::make('View Song', fn($song) => $song->url())
/* testPrecededByArrowFunctionInArray */
->onlyOnDetail(),
new Panel('Information', [
Text::make('Title')
]),
];
switch ($foo) {
/* testCaseStatement */
case 1:
/* testInsideCaseStatement */
$var = doSomething();
/* testInsideCaseBreakStatement */
break 1;
case 2:
/* testInsideCaseContinueStatement */
continue 1;
case 3:
/* testInsideCaseReturnStatement */
return false;
case 4:
/* testInsideCaseExitStatement */
exit(1);
case 5:
/* testInsideCaseThrowStatement */
throw new Exception();
case 6:
$var = doSomething();
/* testInsideCaseGotoStatement */
goto myLabel;
case 7:
/* testInsideCaseFullyQualifiedDieStatement */
\die(1);
/* testDefaultStatement */
default:
/* testInsideDefaultContinueStatement */
continue $var;
}
myLabel:
do_something();
match ($var) {
true =>
/* test437ClosureDeclaration */
function ($var) {
/* test437EchoNestedWithinClosureWithinMatch */
echo $var, 'text', PHP_EOL;
},
default => false
};
match ($var) {
/* test437NestedLongArrayWithinMatch */
'a' => array( 1, 2.5, $var),
/* test437NestedFunctionCallWithinMatch */
'b' => functionCall( 11, $var, 50.50),
/* test437NestedArrowFunctionWithinMatch */
'c' => fn($p1, /* test437FnSecondParamWithinMatch */ $p2) => $p1 + $p2,
default => false
};
callMe($paramA, match ($var) {
/* test437NestedLongArrayWithinNestedMatch */
'a' => array( 1, 2.5, $var),
/* test437NestedFunctionCallWithinNestedMatch */
'b' => functionCall( 11, $var, 50.50),
/* test437NestedArrowFunctionWithinNestedMatch */
'c' => fn($p1, /* test437FnSecondParamWithinNestedMatch */ $p2) => $p1 + $p2,
default => false
});
match ($var) {
/* test437NestedShortArrayWithinMatch */
'a' => [ 1, 2.5, $var],
default => false
};

View File

@@ -0,0 +1,989 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::findStartOfStatement method.
*
* @author Greg Sherwood <gsherwood@squiz.net>
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @copyright 2019-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
use PHP_CodeSniffer\Util\Tokens;
/**
* Tests for the \PHP_CodeSniffer\Files\File::findStartOfStatement method.
*
* @covers \PHP_CodeSniffer\Files\File::findStartOfStatement
*/
final class FindStartOfStatementTest extends AbstractMethodUnitTest
{
/**
* Test that start of statement is NEVER beyond the "current" token.
*
* @return void
*/
public function testStartIsNeverMoreThanCurrentToken()
{
$tokens = self::$phpcsFile->getTokens();
$errors = [];
for ($i = 0; $i < self::$phpcsFile->numTokens; $i++) {
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
continue;
}
$start = self::$phpcsFile->findStartOfStatement($i);
// Collect all the errors.
if ($start > $i) {
$errors[] = sprintf(
'Start of statement for token %1$d (%2$s: %3$s) on line %4$d is %5$d (%6$s), which is more than %1$d',
$i,
$tokens[$i]['type'],
$tokens[$i]['content'],
$tokens[$i]['line'],
$start,
$tokens[$start]['type']
);
}
}
$this->assertSame([], $errors);
}//end testStartIsNeverMoreThanCurrentToken()
/**
* Test a simple assignment.
*
* @return void
*/
public function testSimpleAssignment()
{
$start = $this->getTargetToken('/* testSimpleAssignment */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
}//end testSimpleAssignment()
/**
* Test a function call.
*
* @return void
*/
public function testFunctionCall()
{
$start = $this->getTargetToken('/* testFunctionCall */', T_CLOSE_PARENTHESIS);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testFunctionCall()
/**
* Test a function call.
*
* @return void
*/
public function testFunctionCallArgument()
{
$start = $this->getTargetToken('/* testFunctionCallArgument */', T_VARIABLE, '$b');
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testFunctionCallArgument()
/**
* Test a direct call to a control structure.
*
* @return void
*/
public function testControlStructure()
{
$start = $this->getTargetToken('/* testControlStructure */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testControlStructure()
/**
* Test the assignment of a closure.
*
* @return void
*/
public function testClosureAssignment()
{
$start = $this->getTargetToken('/* testClosureAssignment */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 11), $found);
}//end testClosureAssignment()
/**
* Test using a heredoc in a function argument.
*
* @return void
*/
public function testHeredocFunctionArg()
{
// Find the start of the function.
$start = $this->getTargetToken('/* testHeredocFunctionArg */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
// Find the start of the heredoc.
$start -= 4;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 4), $found);
// Find the start of the last arg.
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testHeredocFunctionArg()
/**
* Test parts of a switch statement.
*
* @return void
*/
public function testSwitch()
{
// Find the start of the switch.
$start = $this->getTargetToken('/* testSwitch */', T_CLOSE_CURLY_BRACKET);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 47), $found);
// Find the start of default case.
$start -= 5;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
// Find the start of the second case.
$start -= 12;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
// Find the start of the first case.
$start -= 13;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 8), $found);
// Test inside the first case.
$start--;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testSwitch()
/**
* Test statements that are array values.
*
* @return void
*/
public function testStatementAsArrayValue()
{
// Test short array syntax.
$start = $this->getTargetToken('/* testStatementAsArrayValue */', T_STRING, 'Datetime');
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
// Test long array syntax.
$start += 12;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
// Test same statement outside of array.
$start++;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 9), $found);
// Test with an array index.
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 5), $found);
}//end testStatementAsArrayValue()
/**
* Test a use group.
*
* @return void
*/
public function testUseGroup()
{
$start = $this->getTargetToken('/* testUseGroup */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 23), $found);
}//end testUseGroup()
/**
* Test arrow function as array value.
*
* @return void
*/
public function testArrowFunctionArrayValue()
{
$start = $this->getTargetToken('/* testArrowFunctionArrayValue */', T_COMMA);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 7), $found);
}//end testArrowFunctionArrayValue()
/**
* Test static arrow function.
*
* @return void
*/
public function testStaticArrowFunction()
{
$start = $this->getTargetToken('/* testStaticArrowFunction */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 11), $found);
}//end testStaticArrowFunction()
/**
* Test arrow function with return value.
*
* @return void
*/
public function testArrowFunctionReturnValue()
{
$start = $this->getTargetToken('/* testArrowFunctionReturnValue */', T_SEMICOLON);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
}//end testArrowFunctionReturnValue()
/**
* Test arrow function used as a function argument.
*
* @return void
*/
public function testArrowFunctionAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionAsArgument */', T_FN);
$start += 8;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 8), $found);
}//end testArrowFunctionAsArgument()
/**
* Test arrow function with arrays used as a function argument.
*
* @return void
*/
public function testArrowFunctionWithArrayAsArgument()
{
$start = $this->getTargetToken('/* testArrowFunctionWithArrayAsArgument */', T_FN);
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 17), $found);
}//end testArrowFunctionWithArrayAsArgument()
/**
* Test simple match expression case.
*
* @return void
*/
public function testMatchCase()
{
$start = $this->getTargetToken('/* testMatchCase */', T_COMMA);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testMatchCase()
/**
* Test simple match expression default case.
*
* @return void
*/
public function testMatchDefault()
{
$start = $this->getTargetToken('/* testMatchDefault */', T_CONSTANT_ENCAPSED_STRING, "'bar'");
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefault()
/**
* Test multiple comma-separated match expression case values.
*
* @return void
*/
public function testMatchMultipleCase()
{
$start = $this->getTargetToken('/* testMatchMultipleCase */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
$start += 6;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 4), $found);
}//end testMatchMultipleCase()
/**
* Test match expression default case with trailing comma.
*
* @return void
*/
public function testMatchDefaultComma()
{
$start = $this->getTargetToken('/* testMatchDefaultComma */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 3), $found);
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
}//end testMatchDefaultComma()
/**
* Test match expression with function call.
*
* @return void
*/
public function testMatchFunctionCall()
{
$start = $this->getTargetToken('/* testMatchFunctionCall */', T_CLOSE_PARENTHESIS);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 6), $found);
}//end testMatchFunctionCall()
/**
* Test match expression with function call in the arm.
*
* @return void
*/
public function testMatchFunctionCallArm()
{
// Check the first case.
$start = $this->getTargetToken('/* testMatchFunctionCallArm */', T_MATCH_ARROW);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
// Check the second case.
$start += 24;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 18), $found);
}//end testMatchFunctionCallArm()
/**
* Test match expression with closure.
*
* @return void
*/
public function testMatchClosure()
{
$start = $this->getTargetToken('/* testMatchClosure */', T_LNUMBER);
$start += 14;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
$start += 17;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 10), $found);
}//end testMatchClosure()
/**
* Test match expression with array declaration.
*
* @return void
*/
public function testMatchArray()
{
// Start of first case statement.
$start = $this->getTargetToken('/* testMatchArray */', T_LNUMBER);
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
// Comma after first statement.
$start += 11;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 7), $found);
// Start of second case statement.
$start += 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($start, $found);
// Comma after first statement.
$start += 30;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 26), $found);
}//end testMatchArray()
/**
* Test nested match expressions.
*
* @return void
*/
public function testNestedMatch()
{
$start = $this->getTargetToken('/* testNestedMatch */', T_LNUMBER);
$start += 30;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 26), $found);
$start -= 4;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
$start -= 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 2), $found);
}//end testNestedMatch()
/**
* Test PHP open tag.
*
* @return void
*/
public function testOpenTag()
{
$start = $this->getTargetToken('/* testOpenTag */', T_OPEN_TAG);
$start += 2;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testOpenTag()
/**
* Test PHP short open echo tag.
*
* @return void
*/
public function testOpenTagWithEcho()
{
$start = $this->getTargetToken('/* testOpenTagWithEcho */', T_OPEN_TAG_WITH_ECHO);
$start += 3;
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame(($start - 1), $found);
}//end testOpenTagWithEcho()
/**
* Test object call on result of static function call with arrow function as parameter and wrapped within an array.
*
* @link https://github.com/squizlabs/PHP_CodeSniffer/issues/2849
* @link https://github.com/squizlabs/PHP_CodeSniffer/commit/fbf67efc3fc0c2a355f5585d49f4f6fe160ff2f9
*
* @return void
*/
public function testObjectCallPrecededByArrowFunctionAsFunctionCallParameterInArray()
{
$expected = $this->getTargetToken('/* testPrecededByArrowFunctionInArray - Expected */', T_STRING, 'Url');
$start = $this->getTargetToken('/* testPrecededByArrowFunctionInArray */', T_STRING, 'onlyOnDetail');
$found = self::$phpcsFile->findStartOfStatement($start);
$this->assertSame($expected, $found);
}//end testObjectCallPrecededByArrowFunctionAsFunctionCallParameterInArray()
/**
* Test finding the start of a statement inside a switch control structure case/default statement.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targets The token to search for after the test marker.
* @param string|int $expectedTarget Token code of the expected start of statement stack pointer.
*
* @link https://github.com/squizlabs/php_codesniffer/issues/3192
* @link https://github.com/squizlabs/PHP_CodeSniffer/pull/3186/commits/18a0e54735bb9b3850fec266e5f4c50dacf618ea
*
* @dataProvider dataFindStartInsideSwitchCaseDefaultStatements
*
* @return void
*/
public function testFindStartInsideSwitchCaseDefaultStatements($testMarker, $targets, $expectedTarget)
{
$testToken = $this->getTargetToken($testMarker, $targets);
$expected = $this->getTargetToken($testMarker, $expectedTarget);
$found = self::$phpcsFile->findStartOfStatement($testToken);
$this->assertSame($expected, $found);
}//end testFindStartInsideSwitchCaseDefaultStatements()
/**
* Data provider.
*
* @return array<string, array<string, int|string>>
*/
public static function dataFindStartInsideSwitchCaseDefaultStatements()
{
return [
'Case keyword should be start of case statement - case itself' => [
'testMarker' => '/* testCaseStatement */',
'targets' => T_CASE,
'expectedTarget' => T_CASE,
],
'Case keyword should be start of case statement - number (what\'s being compared)' => [
'testMarker' => '/* testCaseStatement */',
'targets' => T_LNUMBER,
'expectedTarget' => T_CASE,
],
'Variable should be start of arbitrary assignment statement - variable itself' => [
'testMarker' => '/* testInsideCaseStatement */',
'targets' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Variable should be start of arbitrary assignment statement - equal sign' => [
'testMarker' => '/* testInsideCaseStatement */',
'targets' => T_EQUAL,
'expectedTarget' => T_VARIABLE,
],
'Variable should be start of arbitrary assignment statement - function call' => [
'testMarker' => '/* testInsideCaseStatement */',
'targets' => T_STRING,
'expectedTarget' => T_VARIABLE,
],
'Break should be start for contents of the break statement - contents' => [
'testMarker' => '/* testInsideCaseBreakStatement */',
'targets' => T_LNUMBER,
'expectedTarget' => T_BREAK,
],
'Continue should be start for contents of the continue statement - contents' => [
'testMarker' => '/* testInsideCaseContinueStatement */',
'targets' => T_LNUMBER,
'expectedTarget' => T_CONTINUE,
],
'Return should be start for contents of the return statement - contents' => [
'testMarker' => '/* testInsideCaseReturnStatement */',
'targets' => T_FALSE,
'expectedTarget' => T_RETURN,
],
'Exit should be start for contents of the exit statement - close parenthesis' => [
// Note: not sure if this is actually correct - should this be the open parenthesis ?
'testMarker' => '/* testInsideCaseExitStatement */',
'targets' => T_CLOSE_PARENTHESIS,
'expectedTarget' => T_EXIT,
],
'Throw should be start for contents of the throw statement - new keyword' => [
'testMarker' => '/* testInsideCaseThrowStatement */',
'targets' => T_NEW,
'expectedTarget' => T_THROW,
],
'Throw should be start for contents of the throw statement - exception name' => [
'testMarker' => '/* testInsideCaseThrowStatement */',
'targets' => T_STRING,
'expectedTarget' => T_THROW,
],
'Throw should be start for contents of the throw statement - close parenthesis' => [
'testMarker' => '/* testInsideCaseThrowStatement */',
'targets' => T_CLOSE_PARENTHESIS,
'expectedTarget' => T_THROW,
],
'Goto should be start for contents of the goto statement - goto label' => [
'testMarker' => '/* testInsideCaseGotoStatement */',
'targets' => T_STRING,
'expectedTarget' => T_GOTO,
],
'Goto should be start for contents of the goto statement - semicolon' => [
'testMarker' => '/* testInsideCaseGotoStatement */',
'targets' => T_SEMICOLON,
'expectedTarget' => T_GOTO,
],
'Namespace separator for "die" should be start for contents - close parenthesis' => [
// Note: not sure if this is actually correct - should this be the open parenthesis ?
'testMarker' => '/* testInsideCaseFullyQualifiedDieStatement */',
'targets' => T_CLOSE_PARENTHESIS,
'expectedTarget' => T_NS_SEPARATOR,
],
'Default keyword should be start of default statement - default itself' => [
'testMarker' => '/* testDefaultStatement */',
'targets' => T_DEFAULT,
'expectedTarget' => T_DEFAULT,
],
'Return should be start for contents of the return statement (inside default) - variable' => [
'testMarker' => '/* testInsideDefaultContinueStatement */',
'targets' => T_VARIABLE,
'expectedTarget' => T_CONTINUE,
],
];
}//end dataFindStartInsideSwitchCaseDefaultStatements()
/**
* Test finding the start of a statement inside a closed scope nested within a match expressions.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $target The token to search for after the test marker.
* @param int|string $expectedTarget Token code of the expected start of statement stack pointer.
*
* @link https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/437
*
* @dataProvider dataFindStartInsideClosedScopeNestedWithinMatch
*
* @return void
*/
public function testFindStartInsideClosedScopeNestedWithinMatch($testMarker, $target, $expectedTarget)
{
$testToken = $this->getTargetToken($testMarker, $target);
$expected = $this->getTargetToken($testMarker, $expectedTarget);
$found = self::$phpcsFile->findStartOfStatement($testToken);
$this->assertSame($expected, $found);
}//end testFindStartInsideClosedScopeNestedWithinMatch()
/**
* Data provider.
*
* @return array<string, array<string, int|string>>
*/
public static function dataFindStartInsideClosedScopeNestedWithinMatch()
{
return [
// These were already working correctly.
'Closure function keyword should be start of closure - closure keyword' => [
'testMarker' => '/* test437ClosureDeclaration */',
'target' => T_CLOSURE,
'expectedTarget' => T_CLOSURE,
],
'Open curly is a statement/expression opener - open curly' => [
'testMarker' => '/* test437ClosureDeclaration */',
'target' => T_OPEN_CURLY_BRACKET,
'expectedTarget' => T_OPEN_CURLY_BRACKET,
],
'Echo should be start for expression - echo keyword' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_ECHO,
'expectedTarget' => T_ECHO,
],
'Echo should be start for expression - variable' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_ECHO,
],
'Echo should be start for expression - comma' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_COMMA,
'expectedTarget' => T_ECHO,
],
// These were not working correctly and would previously return the close curly of the match expression.
'First token after comma in echo expression should be start for expression - text string' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_CONSTANT_ENCAPSED_STRING,
'expectedTarget' => T_CONSTANT_ENCAPSED_STRING,
],
'First token after comma in echo expression - PHP_EOL constant' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_STRING,
'expectedTarget' => T_STRING,
],
'First token after comma in echo expression - semicolon' => [
'testMarker' => '/* test437EchoNestedWithinClosureWithinMatch */',
'target' => T_SEMICOLON,
'expectedTarget' => T_STRING,
],
];
}//end dataFindStartInsideClosedScopeNestedWithinMatch()
/**
* Test finding the start of a statement for a token within a set of parentheses within a match expressions.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $target The token to search for after the test marker.
* @param int|string $expectedTarget Token code of the expected start of statement stack pointer.
*
* @link https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/437
*
* @dataProvider dataFindStartInsideParenthesesNestedWithinMatch
*
* @return void
*/
public function testFindStartInsideParenthesesNestedWithinMatch($testMarker, $target, $expectedTarget)
{
$testToken = $this->getTargetToken($testMarker, $target);
$expected = $this->getTargetToken($testMarker, $expectedTarget);
$found = self::$phpcsFile->findStartOfStatement($testToken);
$this->assertSame($expected, $found);
}//end testFindStartInsideParenthesesNestedWithinMatch()
/**
* Data provider.
*
* @return array<string, array<string, int|string>>
*/
public static function dataFindStartInsideParenthesesNestedWithinMatch()
{
return [
'Array item itself should be start for first array item' => [
'testMarker' => '/* test437NestedLongArrayWithinMatch */',
'target' => T_LNUMBER,
'expectedTarget' => T_LNUMBER,
],
'Array item itself should be start for second array item' => [
'testMarker' => '/* test437NestedLongArrayWithinMatch */',
'target' => T_DNUMBER,
'expectedTarget' => T_DNUMBER,
],
'Array item itself should be start for third array item' => [
'testMarker' => '/* test437NestedLongArrayWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for first param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinMatch */',
'target' => T_LNUMBER,
'expectedTarget' => T_LNUMBER,
],
'Parameter itself should be start for second param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for third param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinMatch */',
'target' => T_DNUMBER,
'expectedTarget' => T_DNUMBER,
],
'Parameter itself should be start for first param declared in arrow function' => [
'testMarker' => '/* test437NestedArrowFunctionWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for second param declared in arrow function' => [
'testMarker' => '/* test437FnSecondParamWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
];
}//end dataFindStartInsideParenthesesNestedWithinMatch()
/**
* Test finding the start of a statement for a token within a set of parentheses within a match expressions,
* which itself is nested within parentheses.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $target The token to search for after the test marker.
* @param int|string $expectedTarget Token code of the expected start of statement stack pointer.
*
* @link https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/437
*
* @dataProvider dataFindStartInsideParenthesesNestedWithinNestedMatch
*
* @return void
*/
public function testFindStartInsideParenthesesNestedWithinNestedMatch($testMarker, $target, $expectedTarget)
{
$testToken = $this->getTargetToken($testMarker, $target);
$expected = $this->getTargetToken($testMarker, $expectedTarget);
$found = self::$phpcsFile->findStartOfStatement($testToken);
$this->assertSame($expected, $found);
}//end testFindStartInsideParenthesesNestedWithinNestedMatch()
/**
* Data provider.
*
* @return array<string, array<string, int|string>>
*/
public static function dataFindStartInsideParenthesesNestedWithinNestedMatch()
{
return [
'Array item itself should be start for first array item' => [
'testMarker' => '/* test437NestedLongArrayWithinNestedMatch */',
'target' => T_LNUMBER,
'expectedTarget' => T_LNUMBER,
],
'Array item itself should be start for second array item' => [
'testMarker' => '/* test437NestedLongArrayWithinNestedMatch */',
'target' => T_DNUMBER,
'expectedTarget' => T_DNUMBER,
],
'Array item itself should be start for third array item' => [
'testMarker' => '/* test437NestedLongArrayWithinNestedMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for first param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinNestedMatch */',
'target' => T_LNUMBER,
'expectedTarget' => T_LNUMBER,
],
'Parameter itself should be start for second param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinNestedMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for third param passed to function call' => [
'testMarker' => '/* test437NestedFunctionCallWithinNestedMatch */',
'target' => T_DNUMBER,
'expectedTarget' => T_DNUMBER,
],
'Parameter itself should be start for first param declared in arrow function' => [
'testMarker' => '/* test437NestedArrowFunctionWithinNestedMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
'Parameter itself should be start for second param declared in arrow function' => [
'testMarker' => '/* test437FnSecondParamWithinNestedMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
];
}//end dataFindStartInsideParenthesesNestedWithinNestedMatch()
/**
* Test finding the start of a statement for a token within a short array within a match expressions.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $target The token to search for after the test marker.
* @param int|string $expectedTarget Token code of the expected start of statement stack pointer.
*
* @link https://github.com/PHPCSStandards/PHP_CodeSniffer/issues/437
*
* @dataProvider dataFindStartInsideShortArrayNestedWithinMatch
*
* @return void
*/
public function testFindStartInsideShortArrayNestedWithinMatch($testMarker, $target, $expectedTarget)
{
$testToken = $this->getTargetToken($testMarker, $target);
$expected = $this->getTargetToken($testMarker, $expectedTarget);
$found = self::$phpcsFile->findStartOfStatement($testToken);
$this->assertSame($expected, $found);
}//end testFindStartInsideShortArrayNestedWithinMatch()
/**
* Data provider.
*
* @return array<string, array<string, int|string>>
*/
public static function dataFindStartInsideShortArrayNestedWithinMatch()
{
return [
'Array item itself should be start for first array item' => [
'testMarker' => '/* test437NestedShortArrayWithinMatch */',
'target' => T_LNUMBER,
'expectedTarget' => T_LNUMBER,
],
'Array item itself should be start for second array item' => [
'testMarker' => '/* test437NestedShortArrayWithinMatch */',
'target' => T_DNUMBER,
'expectedTarget' => T_DNUMBER,
],
'Array item itself should be start for third array item' => [
'testMarker' => '/* test437NestedShortArrayWithinMatch */',
'target' => T_VARIABLE,
'expectedTarget' => T_VARIABLE,
],
];
}//end dataFindStartInsideShortArrayNestedWithinMatch()
}//end class

View File

@@ -0,0 +1,58 @@
<?php
/* testNotAClass */
interface NotAClass {}
/* testAnonClass */
$anon = new class() {};
/* testEnum */
enum NotAClassEither {}
/* testClassWithoutProperties */
class ClassWithoutProperties {}
/* testAbstractClass */
abstract class AbstractClass {}
/* testFinalClass */
final class FinalClass {}
/* testReadonlyClass */
readonly class ReadOnlyClass {}
/* testFinalReadonlyClass */
final readonly class FinalReadOnlyClass extends Foo {}
/* testReadonlyFinalClass */
readonly /*comment*/ final class ReadOnlyFinalClass {}
/* testAbstractReadonlyClass */
abstract readonly class AbstractReadOnlyClass {}
/* testReadonlyAbstractClass */
readonly
abstract
class ReadOnlyAbstractClass {}
/* testWithCommentsAndNewLines */
abstract
/* comment */
class ClassWithCommentsAndNewLines {}
/* testWithDocblockWithoutProperties */
/**
* Class docblock.
*
* @package SomePackage
*
* @phpcs:disable Standard.Cat.SniffName -- Just because.
*/
class ClassWithDocblock {}
/* testParseErrorAbstractFinal */
final /* comment */
abstract // Intentional parse error, class cannot both be final and abstract.
class AbstractFinal {}

View File

@@ -0,0 +1,192 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:getClassProperties method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getClassProperties method.
*
* @covers \PHP_CodeSniffer\Files\File::getClassProperties
*/
final class GetClassPropertiesTest extends AbstractMethodUnitTest
{
/**
* Test receiving an expected exception when a non class token is passed.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $tokenType The type of token to look for after the marker.
*
* @dataProvider dataNotAClassException
*
* @return void
*/
public function testNotAClassException($testMarker, $tokenType)
{
$this->expectRunTimeException('$stackPtr must be of type T_CLASS');
$target = $this->getTargetToken($testMarker, $tokenType);
self::$phpcsFile->getClassProperties($target);
}//end testNotAClassException()
/**
* Data provider.
*
* @see testNotAClassException() For the array format.
*
* @return array<string, array<string, string|int>>
*/
public static function dataNotAClassException()
{
return [
'interface' => [
'testMarker' => '/* testNotAClass */',
'tokenType' => T_INTERFACE,
],
'anon-class' => [
'testMarker' => '/* testAnonClass */',
'tokenType' => T_ANON_CLASS,
],
'enum' => [
'testMarker' => '/* testEnum */',
'tokenType' => T_ENUM,
],
];
}//end dataNotAClassException()
/**
* Test retrieving the properties for a class declaration.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array<string, bool> $expected Expected function output.
*
* @dataProvider dataGetClassProperties
*
* @return void
*/
public function testGetClassProperties($testMarker, $expected)
{
$class = $this->getTargetToken($testMarker, T_CLASS);
$result = self::$phpcsFile->getClassProperties($class);
$this->assertSame($expected, $result);
}//end testGetClassProperties()
/**
* Data provider.
*
* @see testGetClassProperties() For the array format.
*
* @return array<string, array<string, string|array<string, bool|int>>>
*/
public static function dataGetClassProperties()
{
return [
'no-properties' => [
'testMarker' => '/* testClassWithoutProperties */',
'expected' => [
'is_abstract' => false,
'is_final' => false,
'is_readonly' => false,
],
],
'abstract' => [
'testMarker' => '/* testAbstractClass */',
'expected' => [
'is_abstract' => true,
'is_final' => false,
'is_readonly' => false,
],
],
'final' => [
'testMarker' => '/* testFinalClass */',
'expected' => [
'is_abstract' => false,
'is_final' => true,
'is_readonly' => false,
],
],
'readonly' => [
'testMarker' => '/* testReadonlyClass */',
'expected' => [
'is_abstract' => false,
'is_final' => false,
'is_readonly' => true,
],
],
'final-readonly' => [
'testMarker' => '/* testFinalReadonlyClass */',
'expected' => [
'is_abstract' => false,
'is_final' => true,
'is_readonly' => true,
],
],
'readonly-final' => [
'testMarker' => '/* testReadonlyFinalClass */',
'expected' => [
'is_abstract' => false,
'is_final' => true,
'is_readonly' => true,
],
],
'abstract-readonly' => [
'testMarker' => '/* testAbstractReadonlyClass */',
'expected' => [
'is_abstract' => true,
'is_final' => false,
'is_readonly' => true,
],
],
'readonly-abstract' => [
'testMarker' => '/* testReadonlyAbstractClass */',
'expected' => [
'is_abstract' => true,
'is_final' => false,
'is_readonly' => true,
],
],
'comments-and-new-lines' => [
'testMarker' => '/* testWithCommentsAndNewLines */',
'expected' => [
'is_abstract' => true,
'is_final' => false,
'is_readonly' => false,
],
],
'no-properties-with-docblock' => [
'testMarker' => '/* testWithDocblockWithoutProperties */',
'expected' => [
'is_abstract' => false,
'is_final' => false,
'is_readonly' => false,
],
],
'abstract-final-parse-error' => [
'testMarker' => '/* testParseErrorAbstractFinal */',
'expected' => [
'is_abstract' => true,
'is_final' => true,
'is_readonly' => false,
],
],
];
}//end dataGetClassProperties()
}//end class

View File

@@ -0,0 +1,91 @@
<?php
/* testStartPoint */
/* condition 0: namespace */
namespace Conditions\HorribleCode {
/* condition 1: if */
if (!function_exists('letsGetSerious') ) {
/* condition 2: function */
function letsGetSerious() {
/* condition 3-1: if */
if (isset($loadthis)) {
doing_something();
/* condition 3-2: else */
} else {
/* condition 4: if */
if (!class_exists('SeriouslyNestedClass')) {
/* condition 5: nested class */
class SeriouslyNestedClass extends SomeOtherClass {
/* condition 6: class method */
public function SeriouslyNestedMethod(/* testSeriouslyNestedMethod */ $param) {
/* condition 7: switch */
switch ($param) {
/* condition 8a: case */
case 'testing':
/* condition 9: while */
while ($a < 10 ) {
/* condition 10-1: if */
if ($a === $b) {
/* condition 11-1: nested anonymous class */
return new class() {
/* condition 12: nested anonymous class method */
private function DidSomeoneSayNesting() {
/* condition 13: closure */
$c = function() {
/* testDeepestNested */
return 'closure';
};
}
};
/* condition 10-2: elseif */
} elseif($bool) {
echo 'hello world';
}
/* condition 10-3: foreach */
foreach ($array as $k => $v) {
/* condition 11-2: try */
try {
--$k;
/* condition 11-3: catch */
} catch (Exception $e) {
/* testInException */
echo 'oh darn';
/* condition 11-4: finally */
} finally {
return true;
}
}
$a++;
}
break;
/* condition 8b: default */
default:
/* testInDefault */
$return = 'nada';
return $return;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,494 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File:getCondition and \PHP_CodeSniffer\Files\File:hasCondition methods.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
use PHP_CodeSniffer\Util\Tokens;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getCondition and \PHP_CodeSniffer\Files\File:hasCondition methods.
*
* @covers \PHP_CodeSniffer\Files\File::getCondition
* @covers \PHP_CodeSniffer\Files\File::hasCondition
*/
final class GetConditionTest extends AbstractMethodUnitTest
{
/**
* List of all the test markers with their target token in the test case file.
*
* - The startPoint token is left out as it is tested separately.
* - The key is the type of token to look for after the test marker.
*
* @var array<int|string, string>
*/
protected static $testTargets = [
T_VARIABLE => '/* testSeriouslyNestedMethod */',
T_RETURN => '/* testDeepestNested */',
T_ECHO => '/* testInException */',
T_CONSTANT_ENCAPSED_STRING => '/* testInDefault */',
];
/**
* List of all the condition markers in the test case file.
*
* @var array<string>
*/
protected $conditionMarkers = [
'/* condition 0: namespace */',
'/* condition 1: if */',
'/* condition 2: function */',
'/* condition 3-1: if */',
'/* condition 3-2: else */',
'/* condition 4: if */',
'/* condition 5: nested class */',
'/* condition 6: class method */',
'/* condition 7: switch */',
'/* condition 8a: case */',
'/* condition 9: while */',
'/* condition 10-1: if */',
'/* condition 11-1: nested anonymous class */',
'/* condition 12: nested anonymous class method */',
'/* condition 13: closure */',
'/* condition 10-2: elseif */',
'/* condition 10-3: foreach */',
'/* condition 11-2: try */',
'/* condition 11-3: catch */',
'/* condition 11-4: finally */',
'/* condition 8b: default */',
];
/**
* Base array with all the scope opening tokens.
*
* This array is merged with expected result arrays for various unit tests
* to make sure all possible conditions are tested.
*
* This array should be kept in sync with the Tokens::$scopeOpeners array.
* This array isn't auto-generated based on the array in Tokens as for these
* tests we want to have access to the token constant names, not just their values.
*
* @var array<string, bool>
*/
protected $conditionDefaults = [
'T_CLASS' => false,
'T_ANON_CLASS' => false,
'T_INTERFACE' => false,
'T_TRAIT' => false,
'T_NAMESPACE' => false,
'T_FUNCTION' => false,
'T_CLOSURE' => false,
'T_IF' => false,
'T_SWITCH' => false,
'T_CASE' => false,
'T_DECLARE' => false,
'T_DEFAULT' => false,
'T_WHILE' => false,
'T_ELSE' => false,
'T_ELSEIF' => false,
'T_FOR' => false,
'T_FOREACH' => false,
'T_DO' => false,
'T_TRY' => false,
'T_CATCH' => false,
'T_FINALLY' => false,
'T_PROPERTY' => false,
'T_OBJECT' => false,
'T_USE' => false,
];
/**
* Cache for the test token stack pointers.
*
* @var array<string, int>
*/
protected static $testTokens = [];
/**
* Cache for the marker token stack pointers.
*
* @var array<string, int>
*/
protected static $markerTokens = [];
/**
* Set up the token position caches for the tests.
*
* Retrieves the test tokens and marker token stack pointer positions
* only once and caches them as they won't change between the tests anyway.
*
* @before
*
* @return void
*/
protected function setUpCaches()
{
if (empty(self::$testTokens) === true) {
foreach (self::$testTargets as $targetToken => $marker) {
self::$testTokens[$marker] = $this->getTargetToken($marker, $targetToken);
}
}
if (empty(self::$markerTokens) === true) {
foreach ($this->conditionMarkers as $marker) {
self::$markerTokens[$marker] = $this->getTargetToken($marker, Tokens::$scopeOpeners);
}
}
}//end setUpCaches()
/**
* Test passing a non-existent token pointer.
*
* @return void
*/
public function testNonExistentToken()
{
$result = self::$phpcsFile->getCondition(100000, T_CLASS);
$this->assertFalse($result);
$result = self::$phpcsFile->hasCondition(100000, T_IF);
$this->assertFalse($result);
}//end testNonExistentToken()
/**
* Test passing a non conditional token.
*
* @return void
*/
public function testNonConditionalToken()
{
$targetType = T_STRING;
$stackPtr = $this->getTargetToken('/* testStartPoint */', $targetType);
$result = self::$phpcsFile->getCondition($stackPtr, T_IF);
$this->assertFalse($result);
$result = self::$phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens);
$this->assertFalse($result);
}//end testNonConditionalToken()
/**
* Test retrieving a specific condition from a tokens "conditions" array.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array<string, string> $expectedResults Array with the condition token type to search for as key
* and the marker for the expected stack pointer result as a value.
*
* @dataProvider dataGetCondition
*
* @return void
*/
public function testGetCondition($testMarker, $expectedResults)
{
$stackPtr = self::$testTokens[$testMarker];
// Add expected results for all test markers not listed in the data provider.
$expectedResults += $this->conditionDefaults;
foreach ($expectedResults as $conditionType => $expected) {
if (is_string($expected) === true) {
$expected = self::$markerTokens[$expected];
}
$result = self::$phpcsFile->getCondition($stackPtr, constant($conditionType));
$this->assertSame(
$expected,
$result,
"Assertion failed for test marker '{$testMarker}' with condition {$conditionType}"
);
}
}//end testGetCondition()
/**
* Data provider.
*
* Only the conditions which are expected to be *found* need to be listed here.
* All other potential conditions will automatically also be tested and will expect
* `false` as a result.
*
* @see testGetCondition() For the array format.
*
* @return array<string, array<string, string|array<string, string>>>
*/
public static function dataGetCondition()
{
return [
'testSeriouslyNestedMethod' => [
'testMarker' => '/* testSeriouslyNestedMethod */',
'expectedResults' => [
'T_CLASS' => '/* condition 5: nested class */',
'T_NAMESPACE' => '/* condition 0: namespace */',
'T_FUNCTION' => '/* condition 2: function */',
'T_IF' => '/* condition 1: if */',
'T_ELSE' => '/* condition 3-2: else */',
],
],
'testDeepestNested' => [
'testMarker' => '/* testDeepestNested */',
'expectedResults' => [
'T_CLASS' => '/* condition 5: nested class */',
'T_ANON_CLASS' => '/* condition 11-1: nested anonymous class */',
'T_NAMESPACE' => '/* condition 0: namespace */',
'T_FUNCTION' => '/* condition 2: function */',
'T_CLOSURE' => '/* condition 13: closure */',
'T_IF' => '/* condition 1: if */',
'T_SWITCH' => '/* condition 7: switch */',
'T_CASE' => '/* condition 8a: case */',
'T_WHILE' => '/* condition 9: while */',
'T_ELSE' => '/* condition 3-2: else */',
],
],
'testInException' => [
'testMarker' => '/* testInException */',
'expectedResults' => [
'T_CLASS' => '/* condition 5: nested class */',
'T_NAMESPACE' => '/* condition 0: namespace */',
'T_FUNCTION' => '/* condition 2: function */',
'T_IF' => '/* condition 1: if */',
'T_SWITCH' => '/* condition 7: switch */',
'T_CASE' => '/* condition 8a: case */',
'T_WHILE' => '/* condition 9: while */',
'T_ELSE' => '/* condition 3-2: else */',
'T_FOREACH' => '/* condition 10-3: foreach */',
'T_CATCH' => '/* condition 11-3: catch */',
],
],
'testInDefault' => [
'testMarker' => '/* testInDefault */',
'expectedResults' => [
'T_CLASS' => '/* condition 5: nested class */',
'T_NAMESPACE' => '/* condition 0: namespace */',
'T_FUNCTION' => '/* condition 2: function */',
'T_IF' => '/* condition 1: if */',
'T_SWITCH' => '/* condition 7: switch */',
'T_DEFAULT' => '/* condition 8b: default */',
'T_ELSE' => '/* condition 3-2: else */',
],
],
];
}//end dataGetCondition()
/**
* Test retrieving a specific condition from a tokens "conditions" array.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array<string, string> $expectedResults Array with the condition token type to search for as key
* and the marker for the expected stack pointer result as a value.
*
* @dataProvider dataGetConditionReversed
*
* @return void
*/
public function testGetConditionReversed($testMarker, $expectedResults)
{
$stackPtr = self::$testTokens[$testMarker];
// Add expected results for all test markers not listed in the data provider.
$expectedResults += $this->conditionDefaults;
foreach ($expectedResults as $conditionType => $expected) {
if (is_string($expected) === true) {
$expected = self::$markerTokens[$expected];
}
$result = self::$phpcsFile->getCondition($stackPtr, constant($conditionType), false);
$this->assertSame(
$expected,
$result,
"Assertion failed for test marker '{$testMarker}' with condition {$conditionType} (reversed)"
);
}
}//end testGetConditionReversed()
/**
* Data provider.
*
* Only the conditions which are expected to be *found* need to be listed here.
* All other potential conditions will automatically also be tested and will expect
* `false` as a result.
*
* @see testGetConditionReversed() For the array format.
*
* @return array<string, array<string, string|array<string, string>>>
*/
public static function dataGetConditionReversed()
{
$data = self::dataGetCondition();
// Set up the data for the reversed results.
$data['testSeriouslyNestedMethod']['expectedResults']['T_IF'] = '/* condition 4: if */';
$data['testDeepestNested']['expectedResults']['T_FUNCTION'] = '/* condition 12: nested anonymous class method */';
$data['testDeepestNested']['expectedResults']['T_IF'] = '/* condition 10-1: if */';
$data['testInException']['expectedResults']['T_FUNCTION'] = '/* condition 6: class method */';
$data['testInException']['expectedResults']['T_IF'] = '/* condition 4: if */';
$data['testInDefault']['expectedResults']['T_FUNCTION'] = '/* condition 6: class method */';
$data['testInDefault']['expectedResults']['T_IF'] = '/* condition 4: if */';
return $data;
}//end dataGetConditionReversed()
/**
* Test whether a token has a condition of a certain type.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param array<string, bool> $expectedResults Array with the condition token type to search for as key
* and the expected result as a value.
*
* @dataProvider dataHasCondition
*
* @return void
*/
public function testHasCondition($testMarker, $expectedResults)
{
$stackPtr = self::$testTokens[$testMarker];
// Add expected results for all test markers not listed in the data provider.
$expectedResults += $this->conditionDefaults;
foreach ($expectedResults as $conditionType => $expected) {
$result = self::$phpcsFile->hasCondition($stackPtr, constant($conditionType));
$this->assertSame(
$expected,
$result,
"Assertion failed for test marker '{$testMarker}' with condition {$conditionType}"
);
}
}//end testHasCondition()
/**
* Data Provider.
*
* Only list the "true" conditions in the $results array.
* All other potential conditions will automatically also be tested
* and will expect "false" as a result.
*
* @see testHasCondition() For the array format.
*
* @return array<string, array<string, string|array<string, bool>>>
*/
public static function dataHasCondition()
{
return [
'testSeriouslyNestedMethod' => [
'testMarker' => '/* testSeriouslyNestedMethod */',
'expectedResults' => [
'T_CLASS' => true,
'T_NAMESPACE' => true,
'T_FUNCTION' => true,
'T_IF' => true,
'T_ELSE' => true,
],
],
'testDeepestNested' => [
'testMarker' => '/* testDeepestNested */',
'expectedResults' => [
'T_CLASS' => true,
'T_ANON_CLASS' => true,
'T_NAMESPACE' => true,
'T_FUNCTION' => true,
'T_CLOSURE' => true,
'T_IF' => true,
'T_SWITCH' => true,
'T_CASE' => true,
'T_WHILE' => true,
'T_ELSE' => true,
],
],
'testInException' => [
'testMarker' => '/* testInException */',
'expectedResults' => [
'T_CLASS' => true,
'T_NAMESPACE' => true,
'T_FUNCTION' => true,
'T_IF' => true,
'T_SWITCH' => true,
'T_CASE' => true,
'T_WHILE' => true,
'T_ELSE' => true,
'T_FOREACH' => true,
'T_CATCH' => true,
],
],
'testInDefault' => [
'testMarker' => '/* testInDefault */',
'expectedResults' => [
'T_CLASS' => true,
'T_NAMESPACE' => true,
'T_FUNCTION' => true,
'T_IF' => true,
'T_SWITCH' => true,
'T_DEFAULT' => true,
'T_ELSE' => true,
],
],
];
}//end dataHasCondition()
/**
* Test whether a token has a condition of a certain type, with multiple allowed possibilities.
*
* @return void
*/
public function testHasConditionMultipleTypes()
{
$stackPtr = self::$testTokens['/* testInException */'];
$result = self::$phpcsFile->hasCondition($stackPtr, [T_TRY, T_FINALLY]);
$this->assertFalse(
$result,
'Failed asserting that "testInException" does not have a "try" nor a "finally" condition'
);
$result = self::$phpcsFile->hasCondition($stackPtr, [T_TRY, T_CATCH, T_FINALLY]);
$this->assertTrue(
$result,
'Failed asserting that "testInException" has a "try", "catch" or "finally" condition'
);
$stackPtr = self::$testTokens['/* testSeriouslyNestedMethod */'];
$result = self::$phpcsFile->hasCondition($stackPtr, [T_ANON_CLASS, T_CLOSURE]);
$this->assertFalse(
$result,
'Failed asserting that "testSeriouslyNestedMethod" does not have an anonymous class nor a closure condition'
);
$result = self::$phpcsFile->hasCondition($stackPtr, Tokens::$ooScopeTokens);
$this->assertTrue(
$result,
'Failed asserting that "testSeriouslyNestedMethod" has an OO Scope token condition'
);
}//end testHasConditionMultipleTypes()
}//end class

View File

@@ -0,0 +1,23 @@
/* testInvalidTokenPassed */
print something;
var object =
{
/* testClosure */
propertyName: function () {}
}
/* testFunction */
function functionName() {}
/* testClass */
class ClassName
{
/* testMethod */
methodName() {
return false;
}
}
/* testFunctionUnicode */
function π() {}

View File

@@ -0,0 +1,158 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getDeclarationName method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getDeclarationName method.
*
* @covers \PHP_CodeSniffer\Files\File::getDeclarationName
*/
final class GetDeclarationNameJSTest extends AbstractMethodUnitTest
{
/**
* The file extension of the test case file (without leading dot).
*
* @var string
*/
protected static $fileExtension = 'js';
/**
* Test receiving an expected exception when a non-supported token is passed.
*
* @return void
*/
public function testInvalidTokenPassed()
{
$this->expectRunTimeException('Token type "T_STRING" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
$target = $this->getTargetToken('/* testInvalidTokenPassed */', T_STRING);
self::$phpcsFile->getDeclarationName($target);
}//end testInvalidTokenPassed()
/**
* Test receiving "null" when passed an anonymous construct or in case of a parse error.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @dataProvider dataGetDeclarationNameNull
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertNull($result);
}//end testGetDeclarationNameNull()
/**
* Data provider.
*
* @see GetDeclarationNameTest::testGetDeclarationNameNull()
*
* @return array<string, array<string, int|string>>
*/
public static function dataGetDeclarationNameNull()
{
return [
'closure' => [
'testMarker' => '/* testClosure */',
'targetType' => T_CLOSURE,
],
];
}//end dataGetDeclarationNameNull()
/**
* Test retrieving the name of a function or OO structure.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expected Expected function output.
* @param array<int|string>|null $targetType Token type of the token to get as stackPtr.
*
* @dataProvider dataGetDeclarationName
*
* @return void
*/
public function testGetDeclarationName($testMarker, $expected, $targetType=null)
{
if (isset($targetType) === false) {
$targetType = [
T_CLASS,
T_INTERFACE,
T_TRAIT,
T_ENUM,
T_FUNCTION,
];
}
$target = $this->getTargetToken($testMarker, $targetType);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertSame($expected, $result);
}//end testGetDeclarationName()
/**
* Data provider.
*
* @see GetDeclarationNameTest::testGetDeclarationName()
*
* @return array<string, array<string, string|array<int|string>>>
*/
public static function dataGetDeclarationName()
{
return [
'function' => [
'testMarker' => '/* testFunction */',
'expected' => 'functionName',
],
'class' => [
'testMarker' => '/* testClass */',
'expected' => 'ClassName',
'targetType' => [
T_CLASS,
T_STRING,
],
],
'function-unicode-name' => [
'testMarker' => '/* testFunctionUnicode */',
'expected' => 'π',
],
];
}//end dataGetDeclarationName()
/**
* Test retrieving the name of JS ES6 class method.
*
* @return void
*/
public function testGetDeclarationNameES6Method()
{
$target = $this->getTargetToken('/* testMethod */', [T_CLASS, T_INTERFACE, T_TRAIT, T_FUNCTION]);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertSame('methodName', $result);
}//end testGetDeclarationNameES6Method()
}//end class

View File

@@ -0,0 +1,5 @@
<?php
/* testLiveCoding */
// Intentional parse error. This must be the only test in the file.
function // Comment.

View File

@@ -0,0 +1,37 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getDeclarationName method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getDeclarationName method.
*
* @covers \PHP_CodeSniffer\Files\File::getDeclarationName
*/
final class GetDeclarationNameParseError1Test extends AbstractMethodUnitTest
{
/**
* Test receiving "null" in case of a parse error.
*
* @return void
*/
public function testGetDeclarationNameNull()
{
$target = $this->getTargetToken('/* testLiveCoding */', T_FUNCTION);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertNull($result);
}//end testGetDeclarationNameNull()
}//end class

View File

@@ -0,0 +1,6 @@
<?php
/* testLiveCoding */
// Intentional parse error/live coding. This must be the only test in the file.
// Safeguarding that the utility method does not confuse the `string` type with a function name.
$closure = function (string $param) use ($var

View File

@@ -0,0 +1,37 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getDeclarationName method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getDeclarationName method.
*
* @covers \PHP_CodeSniffer\Files\File::getDeclarationName
*/
final class GetDeclarationNameParseError2Test extends AbstractMethodUnitTest
{
/**
* Test receiving "null" in case of a parse error.
*
* @return void
*/
public function testGetDeclarationNameNull()
{
$target = $this->getTargetToken('/* testLiveCoding */', T_FUNCTION);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertNull($result);
}//end testGetDeclarationNameNull()
}//end class

View File

@@ -0,0 +1,98 @@
<?php
/* testInvalidTokenPassed */
echo MY_CONSTANT;
/* testClosure */
$closure = function() {};
/* testAnonClassWithParens */
$anonClass = new class() {};
/* testAnonClassWithParens2 */
$class = new class() {
private $property = 'test';
public function test() {}
};
/* testAnonClassWithoutParens */
$anonClass = new class {};
/* testAnonClassExtendsWithoutParens */
$class = new class extends SomeClass {
private $property = 'test';
public function test() {}
};
/* testFunction */
function functionName() {}
/* testFunctionReturnByRef */
function & functionNameByRef() {}
/* testClass */
abstract class ClassName {
/* testMethod */
public function methodName() {}
/* testAbstractMethod */
abstract protected function abstractMethodName();
/* testMethodReturnByRef */
private function &MethodNameByRef();
}
/* testExtendedClass */
class ExtendedClass extends Foo {}
/* testInterface */
interface InterfaceName {}
/* testTrait */
trait TraitName {
/* testFunctionEndingWithNumber */
function ValidNameEndingWithNumber5(){}
}
/* testClassWithNumber */
class ClassWith1Number implements SomeInterface {}
/* testInterfaceWithNumbers */
interface InterfaceWith12345Numbers extends AnotherInterface {}
/* testClassWithCommentsAndNewLines */
class /* comment */
// phpcs:ignore Standard.Cat.SniffName -- for reasons
ClassWithCommentsAndNewLines {}
/* testFunctionFn */
function fn() {}
/* testPureEnum */
enum Foo
{
case SOME_CASE;
}
/* testBackedEnumSpaceBetweenNameAndColon */
enum Hoo : string
{
case ONE = 'one';
case TWO = 'two';
}
/* testBackedEnumNoSpaceBetweenNameAndColon */
enum Suit: int implements Colorful, CardGame {}
/* testFunctionReturnByRefWithReservedKeywordEach */
function &each() {}
/* testFunctionReturnByRefWithReservedKeywordParent */
function &parent() {}
/* testFunctionReturnByRefWithReservedKeywordSelf */
function &self() {}
/* testFunctionReturnByRefWithReservedKeywordStatic */
function &static() {}

View File

@@ -0,0 +1,221 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getDeclarationName method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getDeclarationName method.
*
* @covers \PHP_CodeSniffer\Files\File::getDeclarationName
*/
final class GetDeclarationNameTest extends AbstractMethodUnitTest
{
/**
* Test receiving an expected exception when a non-supported token is passed.
*
* @return void
*/
public function testInvalidTokenPassed()
{
$this->expectRunTimeException('Token type "T_STRING" is not T_FUNCTION, T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM');
$target = $this->getTargetToken('/* testInvalidTokenPassed */', T_STRING);
self::$phpcsFile->getDeclarationName($target);
}//end testInvalidTokenPassed()
/**
* Test receiving "null" when passed an anonymous construct or in case of a parse error.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $targetType Token type of the token to get as stackPtr.
*
* @dataProvider dataGetDeclarationNameNull
*
* @return void
*/
public function testGetDeclarationNameNull($testMarker, $targetType)
{
$target = $this->getTargetToken($testMarker, $targetType);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertNull($result);
}//end testGetDeclarationNameNull()
/**
* Data provider.
*
* @see testGetDeclarationNameNull() For the array format.
*
* @return array<string, array<string, int|string>>
*/
public static function dataGetDeclarationNameNull()
{
return [
'closure' => [
'testMarker' => '/* testClosure */',
'targetType' => T_CLOSURE,
],
'anon-class-with-parentheses' => [
'testMarker' => '/* testAnonClassWithParens */',
'targetType' => T_ANON_CLASS,
],
'anon-class-with-parentheses-2' => [
'testMarker' => '/* testAnonClassWithParens2 */',
'targetType' => T_ANON_CLASS,
],
'anon-class-without-parentheses' => [
'testMarker' => '/* testAnonClassWithoutParens */',
'targetType' => T_ANON_CLASS,
],
'anon-class-extends-without-parentheses' => [
'testMarker' => '/* testAnonClassExtendsWithoutParens */',
'targetType' => T_ANON_CLASS,
],
];
}//end dataGetDeclarationNameNull()
/**
* Test retrieving the name of a function or OO structure.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param string $expected Expected function output.
* @param int|string|null $targetType Token type of the token to get as stackPtr.
*
* @dataProvider dataGetDeclarationName
*
* @return void
*/
public function testGetDeclarationName($testMarker, $expected, $targetType=null)
{
if (isset($targetType) === false) {
$targetType = [
T_CLASS,
T_INTERFACE,
T_TRAIT,
T_ENUM,
T_FUNCTION,
];
}
$target = $this->getTargetToken($testMarker, $targetType);
$result = self::$phpcsFile->getDeclarationName($target);
$this->assertSame($expected, $result);
}//end testGetDeclarationName()
/**
* Data provider.
*
* @see testGetDeclarationName() For the array format.
*
* @return array<string, array<string, string>>
*/
public static function dataGetDeclarationName()
{
return [
'function' => [
'testMarker' => '/* testFunction */',
'expected' => 'functionName',
],
'function-return-by-reference' => [
'testMarker' => '/* testFunctionReturnByRef */',
'expected' => 'functionNameByRef',
],
'class' => [
'testMarker' => '/* testClass */',
'expected' => 'ClassName',
],
'method' => [
'testMarker' => '/* testMethod */',
'expected' => 'methodName',
],
'abstract-method' => [
'testMarker' => '/* testAbstractMethod */',
'expected' => 'abstractMethodName',
],
'method-return-by-reference' => [
'testMarker' => '/* testMethodReturnByRef */',
'expected' => 'MethodNameByRef',
],
'extended-class' => [
'testMarker' => '/* testExtendedClass */',
'expected' => 'ExtendedClass',
],
'interface' => [
'testMarker' => '/* testInterface */',
'expected' => 'InterfaceName',
],
'trait' => [
'testMarker' => '/* testTrait */',
'expected' => 'TraitName',
],
'function-name-ends-with-number' => [
'testMarker' => '/* testFunctionEndingWithNumber */',
'expected' => 'ValidNameEndingWithNumber5',
],
'class-with-numbers-in-name' => [
'testMarker' => '/* testClassWithNumber */',
'expected' => 'ClassWith1Number',
],
'interface-with-numbers-in-name' => [
'testMarker' => '/* testInterfaceWithNumbers */',
'expected' => 'InterfaceWith12345Numbers',
],
'class-with-comments-and-new-lines' => [
'testMarker' => '/* testClassWithCommentsAndNewLines */',
'expected' => 'ClassWithCommentsAndNewLines',
],
'function-named-fn' => [
'testMarker' => '/* testFunctionFn */',
'expected' => 'fn',
],
'enum-pure' => [
'testMarker' => '/* testPureEnum */',
'expected' => 'Foo',
],
'enum-backed-space-between-name-and-colon' => [
'testMarker' => '/* testBackedEnumSpaceBetweenNameAndColon */',
'expected' => 'Hoo',
],
'enum-backed-no-space-between-name-and-colon' => [
'testMarker' => '/* testBackedEnumNoSpaceBetweenNameAndColon */',
'expected' => 'Suit',
],
'function-return-by-reference-with-reserved-keyword-each' => [
'testMarker' => '/* testFunctionReturnByRefWithReservedKeywordEach */',
'expected' => 'each',
],
'function-return-by-reference-with-reserved-keyword-parent' => [
'testMarker' => '/* testFunctionReturnByRefWithReservedKeywordParent */',
'expected' => 'parent',
],
'function-return-by-reference-with-reserved-keyword-self' => [
'testMarker' => '/* testFunctionReturnByRefWithReservedKeywordSelf */',
'expected' => 'self',
],
'function-return-by-reference-with-reserved-keyword-static' => [
'testMarker' => '/* testFunctionReturnByRefWithReservedKeywordStatic */',
'expected' => 'static',
],
];
}//end dataGetDeclarationName()
}//end class

View File

@@ -0,0 +1,427 @@
<?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 // comment
// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
static
$varH = true;
/* testProtectedStatic */
static protected $varI = true;
/* testPrivateStatic */
private static $varJ = true;
/* testNoPrefix */
$varK = true;
/* testPublicStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
public static $varH = true;
/* testProtectedStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
static protected $varI = true;
/* testPrivateStaticWithDocblock */
/**
* Comment here.
*
* @phpcs:ignore Standard.Category.Sniff -- because
* @var boolean
*/
private static $varJ = true;
public float
/* testGroupType 1 */
$x,
/* testGroupType 2 */
$y;
public static ?string
/* testGroupNullableType 1 */
$x = null,
/* testGroupNullableType 2 */
$y = null;
protected static
/* testGroupProtectedStatic 1 */
$varL,
/* testGroupProtectedStatic 2 */
$varM,
/* testGroupProtectedStatic 3 */
$varN;
private
/* testGroupPrivate 1 */
$varO = true,
/* testGroupPrivate 2 */
$varP = array( 'a' => 'a', 'b' => 'b' ),
/* testGroupPrivate 3 */
$varQ = 'string',
/* testGroupPrivate 4 */
$varR = 123,
/* testGroupPrivate 5 */
$varS = ONE / self::THREE,
/* testGroupPrivate 6 */
$varT = [
'a' => 'a',
'b' => 'b'
],
/* testGroupPrivate 7 */
$varU = __DIR__ . "/base";
/* testMethodParam */
public function methodName($param) {
/* testImportedGlobal */
global $importedGlobal = true;
/* testLocalVariable */
$localVariable = true;
}
/* testPropertyAfterMethod */
private static $varV = true;
/* testMessyNullableType */
public /* comment
*/ ? //comment
array $foo = [];
/* testNamespaceType */
public \MyNamespace\MyClass $foo;
/* testNullableNamespaceType 1 */
private ?ClassName $nullableClassType;
/* testNullableNamespaceType 2 */
protected ?Folder\ClassName $nullableClassType2;
/* testMultilineNamespaceType */
public \MyNamespace /** comment *\/ comment */
\MyClass /* comment */
\Foo $foo;
}
interface Base
{
/* testInterfaceProperty */
protected $anonymous;
}
/* testGlobalVariable */
$globalVariable = true;
/* testNotAVariable */
return;
$a = ( $foo == $bar ? new stdClass() :
new class() {
/* testNestedProperty 1 */
public $var = true;
/* testNestedMethodParam 1 */
public function something($var = false) {}
}
);
function_call( 'param', new class {
/* testNestedProperty 2 */
public $year = 2017;
/* testNestedMethodParam 2 */
public function __construct( $open, $post_id ) {}
}, 10, 2 );
class PHP8Mixed {
/* testPHP8MixedTypeHint */
public static miXed $mixed;
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
private ?mixed $nullableMixed;
}
class NSOperatorInType {
/* testNamespaceOperatorTypeHint */
public ?namespace\Name $prop;
}
$anon = class() {
/* testPHP8UnionTypesSimple */
public int|float $unionTypeSimple;
/* testPHP8UnionTypesTwoClasses */
private MyClassA|\Package\MyClassB $unionTypesTwoClasses;
/* testPHP8UnionTypesAllBaseTypes */
protected array|bool|int|float|NULL|object|string $unionTypesAllBaseTypes;
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
var false|mixed|self|parent|iterable|Resource $unionTypesAllPseudoTypes;
/* testPHP8UnionTypesIllegalTypes */
// Intentional fatal error - types which are not allowed for properties, but that's not the concern of the method.
// Note: static is also not allowed as a type, but using static for a property type is not supported by the tokenizer.
public callable|void $unionTypesIllegalTypes;
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
public ?int|float $unionTypesNullable;
/* testPHP8PseudoTypeNull */
// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
public null $pseudoTypeNull;
/* testPHP8PseudoTypeFalse */
// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
public false $pseudoTypeFalse;
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
public bool|FALSE $pseudoTypeFalseAndBool;
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
public object|ClassName $objectAndClass;
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
public iterable|array|Traversable $pseudoTypeIterableAndArray;
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
public int |string| /*comment*/ INT $duplicateTypeInUnion;
/* testPHP81Readonly */
public readonly int $readonly;
/* testPHP81ReadonlyWithNullableType */
public readonly ?array $readonlyWithNullableType;
/* testPHP81ReadonlyWithUnionType */
public readonly string|int $readonlyWithUnionType;
/* testPHP81ReadonlyWithUnionTypeWithNull */
protected ReadOnly string|null $readonlyWithUnionTypeWithNull;
/* testPHP81OnlyReadonlyWithUnionType */
readonly string|int $onlyReadonly;
/* testPHP81OnlyReadonlyWithUnionTypeMultiple */
readonly \InterfaceA|\Sub\InterfaceB|false
$onlyReadonly;
/* testPHP81ReadonlyAndStatic */
readonly private static ?string $readonlyAndStatic;
/* testPHP81ReadonlyMixedCase */
public ReadONLY static $readonlyMixedCase;
};
$anon = class {
/* testPHP8PropertySingleAttribute */
#[PropertyWithAttribute]
public string $foo;
/* testPHP8PropertyMultipleAttributes */
#[PropertyWithAttribute(foo: 'bar'), MyAttribute]
protected ?int|float $bar;
/* testPHP8PropertyMultilineAttribute */
#[
PropertyWithAttribute(/* comment */ 'baz')
]
private mixed $baz;
};
enum Suit
{
/* testEnumProperty */
protected $anonymous;
}
enum Direction implements ArrayAccess
{
case Up;
case Down;
/* testEnumMethodParamNotProperty */
public function offsetGet($val) { ... }
}
$anon = class() {
/* testPHP81IntersectionTypes */
public Foo&Bar $intersectionType;
/* testPHP81MoreIntersectionTypes */
public Foo&Bar&Baz $moreIntersectionTypes;
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method.
public int&string $illegalIntersectionType;
/* testPHP81NullableIntersectionType */
// Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method.
public ?Foo&Bar $nullableIntersectionType;
};
$anon = class() {
/* testPHP82PseudoTypeTrue */
public true $pseudoTypeTrue;
/* testPHP82NullablePseudoTypeTrue */
static protected ?true $pseudoTypeNullableTrue;
/* testPHP82PseudoTypeTrueInUnion */
private int|string|true $pseudoTypeTrueInUnion;
/* testPHP82PseudoTypeFalseAndTrue */
// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method.
readonly true|FALSE $pseudoTypeFalseAndTrue;
};
class WhitespaceAndCommentsInTypes {
/* testUnionTypeWithWhitespaceAndComment */
public int | /*comment*/ string $hasWhitespaceAndComment;
/* testIntersectionTypeWithWhitespaceAndComment */
public \Foo /*comment*/ & Bar $hasWhitespaceAndComment;
}
trait DNFTypes {
/* testPHP82DNFTypeStatic */
public static (Foo&\Bar)|bool $propA;
/* testPHP82DNFTypeReadonlyA */
protected readonly float|(Partially\Qualified&Traversable) $propB;
/* testPHP82DNFTypeReadonlyB */
private readonly (namespace\Foo&Bar)|string $propC;
/* testPHP82DNFTypeIllegalNullable */
// Intentional fatal error - nullable operator cannot be combined with DNF.
var ?(A&\Pck\B)|bool $propD;
}
class WithFinalProperties {
/* testPHP84FinalPublicTypedProp */
final public string $val1;
/* testPHP84FinalProtectedTypedProp */
final protected string $val2;
/* testPHP84FinalMiddleTypedProp */
public final string $val3;
/* testPHP84FinalMiddleStaticTypedProp */
public final static string $val4;
/* testPHP84FinalLastTypedProp */
public readonly final string $val5;
/* testPHP84FinalImplicitVisibilityTypedProp */
final string $val6;
/* testPHP84FinalImplicitVisibilityProp */
final $val7;
/* testPHP84FinalNullableTypedProp */
final public ?string $val8;
/* testPHP84FinalComplexTypedProp */
final public (Foo&\Bar)|bool $val9;
}
class AsymVisibility {
/* testPHP84AsymPublicSetProperty */
public(set) mixed $prop1;
/* testPHP84AsymPublicPublicSetProperty */
public public(set) (A&B)|null $prop2;
/* testPHP84AsymPublicSetPublicProperty */
public(set) public bool $prop3;
/* testPHP84AsymProtectedSetProperty */
protected(set) readonly mixed $prop4;
/* testPHP84AsymPublicProtectedSetProperty */
public protected(set) string $prop5;
/* testPHP84AsymProtectedSetPublicProperty */
protected(set) public ?float $prop6;
/* testPHP84AsymPrivateSetProperty */
private(set) string|int $prop7;
/* testPHP84AsymProtectedPrivateSetProperty */
final protected private(set) $prop8;
/* testPHP84AsymPrivateSetPublicProperty */
private(set) public mixed $prop9;
/* testPHP84IllegalAsymPublicProtectedSetStaticProperty */
public protected(set) static mixed $prop10;
}
abstract class WithAbstractProperties {
/* testPHP84AbstractPublicTypedProp */
abstract public string $val1 { get; }
/* testPHP84AbstractProtectedTypedProp */
abstract protected Union|Type $val2 { set; }
/* testPHP84AbstractMiddleTypedProp */
public abstract Intersection&Type $val3 { get; }
/* testPHP84AbstractImplicitVisibilityTypedProp */
abstract int $val4 { set; }
/* testPHP84AbstractImplicitVisibilityProp */
abstract $val5 { get; }
/* testPHP84AbstractNullableTypedProp */
abstract public ?string $val6 { set; }
/* testPHP84AbstractComplexTypedProp */
abstract protected (Foo&\Bar)|false $val7 { get; }
/* testPHP84IllegalAbstractPrivateProp */
private abstract string $val8 { get; }
/* testPHP84IllegalAbstractReadonlyProp */
public readonly abstract string $val9 { get; }
/* testPHP84IllegalAbstractStaticProp */
public abstract static string $val10 { get; }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
<?php
/* testParseError */
function missingOpenParens // Intentional parse error.

View File

@@ -0,0 +1,38 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getMethodParameters method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File::getMethodParameters method.
*
* @covers \PHP_CodeSniffer\Files\File::getMethodParameters
*/
final class GetMethodParametersParseError1Test extends AbstractMethodUnitTest
{
/**
* Test receiving an empty array when encountering a specific parse error.
*
* @return void
*/
public function testParseError()
{
$target = $this->getTargetToken('/* testParseError */', [T_FUNCTION, T_CLOSURE, T_FN]);
$result = self::$phpcsFile->getMethodParameters($target);
$this->assertSame([], $result);
}//end testParseError()
}//end class

View File

@@ -0,0 +1,4 @@
<?php
/* testParseError */
function missingCloseParens( // Intentional parse error.

View File

@@ -0,0 +1,38 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getMethodParameters method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2019-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File::getMethodParameters method.
*
* @covers \PHP_CodeSniffer\Files\File::getMethodParameters
*/
final class GetMethodParametersParseError2Test extends AbstractMethodUnitTest
{
/**
* Test receiving an empty array when encountering a specific parse error.
*
* @return void
*/
public function testParseError()
{
$target = $this->getTargetToken('/* testParseError */', [T_FUNCTION, T_CLOSURE, T_FN]);
$result = self::$phpcsFile->getMethodParameters($target);
$this->assertSame([], $result);
}//end testParseError()
}//end class

View File

@@ -0,0 +1,352 @@
<?php
/* testImportUse */
use Vendor\Package\Sub as Alias;
/* testImportGroupUse */
use Vendor\Package\Sub\{
ClassA,
ClassB as BAlias,
};
if ($foo) {}
/* testTraitUse */
class TraitUse {
use ImportedTrait;
function methodName() {}
}
/* testNotAFunction */
interface NotAFunction {};
/* testFunctionNoParams */
function noParams() {}
/* 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;
/* testArrowFunctionReturnByRef */
fn&(?string $a) => $b;
/* testArrayDefaultValues */
function arrayDefaultValues($var1 = [], $var2 = array(1, 2, 3) ) {}
/* testConstantDefaultValueSecondParam */
function constantDefaultValueSecondParam($var1, $var2 = M_PI) {}
/* testScalarTernaryExpressionInDefault */
function ternayInDefault( $a = FOO ? 'bar' : 10, ? bool $b ) {}
/* testVariadicFunction */
function variadicFunction( int ... $a ) {}
/* testVariadicByRefFunction */
function variadicByRefFunction( &...$a ) {}
/* testVariadicFunctionClassType */
function variableLengthArgument($unit, DateInterval ...$intervals) {}
/* testNameSpacedTypeDeclaration */
function namespacedClassType( \Package\Sub\ClassName $a, ?Sub\AnotherClass $b ) {}
/* testWithAllTypes */
class testAllTypes {
function allTypes(
?ClassName $a,
self $b,
parent $c,
object $d,
?int $e,
string &$f,
iterable $g,
bool $h = true,
callable $i = 'is_null',
float $j = 1.1,
array ...$k
) {}
}
/* testArrowFunctionWithAllTypes */
$fn = fn(
?ClassName $a,
self $b,
parent $c,
object $d,
?int $e,
string &$f,
iterable $g,
bool $h = true,
callable $i = 'is_null',
float $j = 1.1,
array ...$k
) => $something;
/* testMessyDeclaration */
function messyDeclaration(
// comment
?\MyNS /* comment */
\ SubCat // phpcs:ignore Standard.Cat.Sniff -- for reasons.
\ MyClass $a,
$b /* comment */ = /* comment */ 'default' /* comment*/,
// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
? /*comment*/
bool // phpcs:disable Stnd.Cat.Sniff -- For reasons.
& /*test*/ ... /* phpcs:ignore */ $c
) {}
/* testPHP8MixedTypeHint */
function mixedTypeHint(mixed &...$var1) {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(?Mixed $var1) {}
/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint(?namespace\Name $var1) {}
/* testPHP8UnionTypesSimple */
function unionTypeSimple(int|float $number, self|parent &...$obj) {}
/* testPHP8UnionTypesWithSpreadOperatorAndReference */
function globalFunctionWithSpreadAndReference(float|null &$paramA, string|int ...$paramB ) {}
/* testPHP8UnionTypesSimpleWithBitwiseOrInDefault */
$fn = fn(int|float $var = CONSTANT_A | CONSTANT_B) => $var;
/* testPHP8UnionTypesTwoClasses */
function unionTypesTwoClasses(MyClassA|\Package\MyClassB $var) {}
/* testPHP8UnionTypesAllBaseTypes */
function unionTypesAllBaseTypes(array|bool|callable|int|float|null|object|string $var) {}
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var) {}
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
$closure = function (?int|float $number) {};
/* testPHP8PseudoTypeNull */
// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeNull(null $var = null) {}
/* testPHP8PseudoTypeFalse */
// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeFalse(false $var = false) {}
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
function pseudoTypeFalseAndBool(bool|false $var = false) {}
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
function objectAndClass(object|ClassName $var) {}
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}
class ConstructorPropertyPromotionNoTypes {
/* testPHP8ConstructorPropertyPromotionNoTypes */
public function __construct(
public $x = 0.0,
protected $y = '',
private $z = null,
) {}
}
class ConstructorPropertyPromotionWithTypes {
/* testPHP8ConstructorPropertyPromotionWithTypes */
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
}
class ConstructorPropertyPromotionAndNormalParams {
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
public function __construct(public int $promotedProp, ?int $normalArg) {}
}
class ConstructorPropertyPromotionWithReadOnly {
/* testPHP81ConstructorPropertyPromotionWithReadOnly */
public function __construct(public readonly ?int $promotedProp, ReadOnly private string|bool &$promotedToo) {}
}
class ConstructorPropertyPromotionWithReadOnlyNoTypeDeclaration {
/* testPHP81ConstructorPropertyPromotionWithReadOnlyNoTypeDeclaration */
// Intentional fatal error. Readonly properties MUST be typed.
public function __construct(public readonly $promotedProp, ReadOnly private &$promotedToo) {}
}
class ConstructorPropertyPromotionWithOnlyReadOnly {
/* testPHP81ConstructorPropertyPromotionWithOnlyReadOnly */
public function __construct(readonly Foo&Bar $promotedProp, readonly ?bool $promotedToo,) {}
}
class ConstructorPropertyPromotionWithAsymVisibility {
/* testPHP84ConstructorPropertyPromotionWithAsymVisibility */
public function __construct(
protected(set) string|Book $book,
public private(set) ?Publisher $publisher,
Private(set) PROTECTED Author $author,
readonly public(set) int $pubYear,
protected(set) $illegalMissingType,
) {}
}
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
function globalFunction(private $x) {}
abstract class ConstructorPropertyPromotionAbstractMethod {
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
// Intentional fatal error.
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
// 3. The callable type is not supported for properties, but that's not the concern of this method.
abstract public function __construct(public callable $y, private ...$x);
}
/* testCommentsInParameter */
function commentsInParams(
// Leading comment.
?MyClass /*-*/ & /*-*/.../*-*/ $param /*-*/ = /*-*/ 'default value' . /*-*/ 'second part' // Trailing comment.
) {}
/* testParameterAttributesInFunctionDeclaration */
class ParametersWithAttributes(
public function __construct(
#[\MyExample\MyAttribute] private string $constructorPropPromTypedParamSingleAttribute,
#[MyAttr([1, 2])]
Type|false
$typedParamSingleAttribute,
#[MyAttribute(1234), MyAttribute(5678)] ?int $nullableTypedParamMultiAttribute,
#[WithoutArgument] #[SingleArgument(0)] $nonTypedParamTwoAttributes,
#[MyAttribute(array("key" => "value"))]
&...$otherParam,
) {}
}
/* testPHP8IntersectionTypes */
function intersectionTypes(Foo&Bar $obj1, Boo&Bar $obj2) {}
/* testPHP81IntersectionTypesWithSpreadOperatorAndReference */
function globalFunctionWithSpreadAndReference(Boo&Bar &$paramA, Foo&Bar ...$paramB) {}
/* testPHP81MoreIntersectionTypes */
function moreIntersectionTypes(MyClassA&\Package\MyClassB&\Package\MyClassC $var) {}
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
$closure = function (string&int $numeric_string) {};
/* testPHP81NullableIntersectionTypes */
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (?Foo&Bar $object) {};
/* testPHP82PseudoTypeTrue */
function pseudoTypeTrue(?true $var = true) {}
/* testPHP82PseudoTypeFalseAndTrue */
// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method.
function pseudoTypeFalseAndTrue(true|false $var = true) {}
/* testPHP81NewInInitializers */
function newInInitializers(
TypeA $new = new TypeA(self::CONST_VALUE),
\Package\TypeB $newToo = new \Package\TypeB(10, 'string'),
) {}
/* testPHP82DNFTypes */
function dnfTypes(
#[MyAttribute]
false|(Foo&Bar)|true $obj1,
(\Boo&\Pck\Bar)|(Boo&Baz) $obj2 = new Boo()
) {}
/* testPHP82DNFTypesWithSpreadOperatorAndReference */
function dnfInGlobalFunctionWithSpreadAndReference((Countable&MeMe)|iterable &$paramA, true|(Foo&Bar) ...$paramB) {}
/* testPHP82DNFTypesIllegalNullable */
// Intentional fatal error - nullable operator cannot be combined with DNF.
$dnf_closure = function (? ( MyClassA & /*comment*/ \Package\MyClassB & \Package\MyClassC ) $var): void {};
/* testPHP82DNFTypesInArrow */
$dnf_arrow = fn((Hi&Ho)|FALSE &...$range): string => $a;
/* testFunctionCallFnPHPCS353-354 */
$value = $obj->fn(true);
/* testClosureNoParams */
function() {};
/* testClosure */
function( $a = 'test' ) {};
/* testClosureUseNoParams */
function() use() {};
/* testClosureUse */
function() use( $foo, $bar ) {};
/* testClosureUseWithReference */
$cl = function() use (&$foo, &$bar) {};
/* testFunctionParamListWithTrailingComma */
function trailingComma(
?string $foo /*comment*/ ,
$bar = 0,
) {}
/* testClosureParamListWithTrailingComma */
function(
$foo,
$bar,
) {};
/* testArrowFunctionParamListWithTrailingComma */
$fn = fn( ?int $a , ...$b, ) => $b;
/* testClosureUseWithTrailingComma */
function() use(
$foo /*comment*/ ,
$bar,
) {};
/* testArrowFunctionLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$fn = fn

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
<?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 */ ? // phpcs:ignore Stnd.Cat.Sniff -- For reasons.
array {}
/* testReturnNamespace */
function myFunction(): \MyNamespace\MyClass {}
/* testReturnMultilineNamespace */
// Parse error in PHP 8.0.
function myFunction(): \MyNamespace /** comment *\/ comment */
\MyClass /* comment */
\Foo {}
/* testReturnUnqualifiedName */
private function myFunction(): ?MyClass {}
/* testReturnPartiallyQualifiedName */
function myFunction(): Sub\Level\MyClass {}
}
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;
}
/* testReturnTypeNullableStatic */
function myNullableFunction(): ?static {
return $this;
}
}
/* testPHP8MixedTypeHint */
function mixedTypeHint() :mixed {}
/* testPHP8MixedTypeHintNullable */
// Intentional fatal error - nullability is not allowed with mixed, but that's not the concern of the method.
function mixedTypeHintNullable(): ?mixed {}
/* testNamespaceOperatorTypeHint */
function namespaceOperatorTypeHint() : ?namespace\Name {}
/* testPHP8UnionTypesSimple */
function unionTypeSimple($number) : int|float {}
/* testPHP8UnionTypesTwoClasses */
$fn = fn($var): MyClassA|\Package\MyClassB => $var;
/* testPHP8UnionTypesAllBaseTypes */
function unionTypesAllBaseTypes() : array|bool|callable|int|float|null|Object|string {}
/* testPHP8UnionTypesAllPseudoTypes */
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterable|Resource|void {}
/* testPHP8UnionTypesNullable */
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
$closure = function () use($a) :?int|float {};
/* testPHP8PseudoTypeNull */
// PHP 8.0 - 8.1: Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeNull(): null {}
/* testPHP8PseudoTypeFalse */
// PHP 8.0 - 8.1: Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
function pseudoTypeFalse(): false {}
/* testPHP8PseudoTypeFalseAndBool */
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
function pseudoTypeFalseAndBool(): bool|false {}
/* testPHP8ObjectAndClass */
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
function objectAndClass(): object|ClassName {}
/* testPHP8PseudoTypeIterableAndArray */
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
interface FooBar {
public function pseudoTypeIterableAndArray(): iterable|array|Traversable;
}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion(): int | /*comment*/ string | INT {}
/* testPHP81NeverType */
function never(): never {}
/* testPHP81NullableNeverType */
// Intentional fatal error - nullability is not allowed with never, but that's not the concern of the method.
function nullableNever(): ?never {}
/* testPHP8IntersectionTypes */
function intersectionTypes(): Foo&Bar {}
/* testPHP81MoreIntersectionTypes */
function moreIntersectionTypes(): MyClassA&\Package\MyClassB&\Package\MyClassC {}
/* testPHP81IntersectionArrowFunction */
$fn = fn($var): MyClassA&\Package\MyClassB => $var;
/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
$closure = function (): string&int {};
/* testPHP81NullableIntersectionTypes */
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (): ?Foo&Bar {};
/* testPHP82PseudoTypeTrue */
function pseudoTypeTrue(): ?true {}
/* testPHP82PseudoTypeFalseAndTrue */
// Intentional fatal error - Type contains both true and false, bool should be used instead, but that's not the concern of the method.
function pseudoTypeFalseAndTrue(): true|false {}
/* testPHP82DNFType */
function hasDNFType() : bool|(Foo&Bar)|string {}
abstract class AbstractClass {
/* testPHP82DNFTypeAbstractMethod */
abstract protected function abstractMethodDNFType() : float|(Foo&Bar);
}
/* testPHP82DNFTypeIllegalNullable */
// Intentional fatal error - nullable operator cannot be combined with DNF.
function illegalNullableDNF(): ?(A&\Pck\B)|bool {}
/* testPHP82DNFTypeClosure */
$closure = function() : object|(namespace\Foo&Countable) {};
/* testPHP82DNFTypeFn */
// Intentional fatal error - void type cannot be combined with DNF.
$arrow = fn() : null|(Partially\Qualified&Traversable)|void => do_something();
/* testNotAFunction */
return true;
/* testPhpcsIssue1264 */
function foo() : array {
echo $foo;
}
/* testArrowFunctionArrayReturnValue */
$fn = fn(): array => [a($a, $b)];
/* testArrowFunctionReturnByRef */
fn&(?string $a) : ?string => $b;
/* testFunctionCallFnPHPCS353-354 */
$value = $obj->fn(true);
/* testFunctionDeclarationNestedInTernaryPHPCS2975 */
return (!$a ? [ new class { public function b(): c {} } ] : []);
/* testClosureWithUseNoReturnType */
$closure = function () use($a) /*comment*/ {};
/* testClosureWithUseNoReturnTypeIllegalUseProp */
$closure = function () use ($this->prop){};
/* testClosureWithUseWithReturnType */
$closure = function () use /*comment*/ ($a): Type {};
/* testClosureWithUseMultiParamWithReturnType */
$closure = function () use ($a, &$b, $c, $d, $e, $f, $g): ?array {};
/* testArrowFunctionLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$fn = fn

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
<?php
/* testNamespace */
namespace Foo\Bar\Baz;
/* testUseWithComments */
use Foo /*comment*/ \ Bar
// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
\ Bah;
$cl = function() {
/* testCalculation */
return 1 + 2 +
// Comment.
3 + 4
+ 5 + 6 + 7 > 20;
}
/* testEchoWithTabs */
echo 'foo',
'bar' ,
'baz';
/* testEndOfFile */
echo $foo;

View File

@@ -0,0 +1,334 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\File::getTokensAsString method.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2022-2024 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File:getTokensAsString method.
*
* @covers \PHP_CodeSniffer\Files\File::getTokensAsString
*/
final class GetTokensAsStringTest extends AbstractMethodUnitTest
{
/**
* Test passing a non-existent token pointer.
*
* @return void
*/
public function testNonExistentToken()
{
$this->expectRunTimeException('The $start position for getTokensAsString() must exist in the token stack');
self::$phpcsFile->getTokensAsString(100000, 10);
}//end testNonExistentToken()
/**
* Test passing a non integer `$start`, like the result of a failed $phpcsFile->findNext().
*
* @return void
*/
public function testNonIntegerStart()
{
$this->expectRunTimeException('The $start position for getTokensAsString() must exist in the token stack');
self::$phpcsFile->getTokensAsString(false, 10);
}//end testNonIntegerStart()
/**
* Test passing a non integer `$length`.
*
* @return void
*/
public function testNonIntegerLength()
{
$result = self::$phpcsFile->getTokensAsString(10, false);
$this->assertSame('', $result);
$result = self::$phpcsFile->getTokensAsString(10, 1.5);
$this->assertSame('', $result);
}//end testNonIntegerLength()
/**
* Test passing a zero or negative `$length`.
*
* @return void
*/
public function testLengthEqualToOrLessThanZero()
{
$result = self::$phpcsFile->getTokensAsString(10, -10);
$this->assertSame('', $result);
$result = self::$phpcsFile->getTokensAsString(10, 0);
$this->assertSame('', $result);
}//end testLengthEqualToOrLessThanZero()
/**
* Test passing a `$length` beyond the end of the file.
*
* @return void
*/
public function testLengthBeyondEndOfFile()
{
$semicolon = $this->getTargetToken('/* testEndOfFile */', T_SEMICOLON);
$result = self::$phpcsFile->getTokensAsString($semicolon, 20);
$this->assertSame(
';
',
$result
);
}//end testLengthBeyondEndOfFile()
/**
* Test getting a token set as a string.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $startTokenType The type of token(s) to look for for the start of the string.
* @param int $length Token length to get.
* @param string $expected The expected function return value.
*
* @dataProvider dataGetTokensAsString
*
* @return void
*/
public function testGetTokensAsString($testMarker, $startTokenType, $length, $expected)
{
$start = $this->getTargetToken($testMarker, $startTokenType);
$result = self::$phpcsFile->getTokensAsString($start, $length);
$this->assertSame($expected, $result);
}//end testGetTokensAsString()
/**
* Data provider.
*
* @see testGetTokensAsString() For the array format.
*
* @return array<string, array<string, string|int>>
*/
public static function dataGetTokensAsString()
{
return [
'length-0' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 0,
'expected' => '',
],
'length-1' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 1,
'expected' => '1',
],
'length-2' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 2,
'expected' => '1 ',
],
'length-3' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 3,
'expected' => '1 +',
],
'length-4' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 4,
'expected' => '1 + ',
],
'length-5' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 5,
'expected' => '1 + 2',
],
'length-6' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 6,
'expected' => '1 + 2 ',
],
'length-7' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 7,
'expected' => '1 + 2 +',
],
'length-8' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 8,
'expected' => '1 + 2 +
',
],
'length-9' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 9,
'expected' => '1 + 2 +
',
],
'length-10' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 10,
'expected' => '1 + 2 +
// Comment.
',
],
'length-11' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 11,
'expected' => '1 + 2 +
// Comment.
',
],
'length-12' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 12,
'expected' => '1 + 2 +
// Comment.
3',
],
'length-13' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 13,
'expected' => '1 + 2 +
// Comment.
3 ',
],
'length-14' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 14,
'expected' => '1 + 2 +
// Comment.
3 +',
],
'length-34' => [
'testMarker' => '/* testCalculation */',
'startTokenType' => T_LNUMBER,
'length' => 34,
'expected' => '1 + 2 +
// Comment.
3 + 4
+ 5 + 6 + 7 > 20;',
],
'namespace' => [
'testMarker' => '/* testNamespace */',
'startTokenType' => T_NAMESPACE,
'length' => 8,
'expected' => 'namespace Foo\Bar\Baz;',
],
'use-with-comments' => [
'testMarker' => '/* testUseWithComments */',
'startTokenType' => T_USE,
'length' => 17,
'expected' => 'use Foo /*comment*/ \ Bar
// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
\ Bah;',
],
'echo-with-tabs' => [
'testMarker' => '/* testEchoWithTabs */',
'startTokenType' => T_ECHO,
'length' => 13,
'expected' => 'echo \'foo\',
\'bar\' ,
\'baz\';',
],
'end-of-file' => [
'testMarker' => '/* testEndOfFile */',
'startTokenType' => T_ECHO,
'length' => 4,
'expected' => 'echo $foo;',
],
];
}//end dataGetTokensAsString()
/**
* Test getting a token set as a string with the original, non tab-replaced content.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
* @param int|string $startTokenType The type of token(s) to look for for the start of the string.
* @param int $length Token length to get.
* @param string $expected The expected function return value.
*
* @dataProvider dataGetOrigContent
*
* @return void
*/
public function testGetOrigContent($testMarker, $startTokenType, $length, $expected)
{
$start = $this->getTargetToken($testMarker, $startTokenType);
$result = self::$phpcsFile->getTokensAsString($start, $length, true);
$this->assertSame($expected, $result);
}//end testGetOrigContent()
/**
* Data provider.
*
* @see testGetOrigContent() For the array format.
*
* @return array<string, array<string, string|int>>
*/
public static function dataGetOrigContent()
{
return [
'use-with-comments' => [
'testMarker' => '/* testUseWithComments */',
'startTokenType' => T_USE,
'length' => 17,
'expected' => 'use Foo /*comment*/ \ Bar
// phpcs:ignore Stnd.Cat.Sniff -- For reasons.
\ Bah;',
],
'echo-with-tabs' => [
'testMarker' => '/* testEchoWithTabs */',
'startTokenType' => T_ECHO,
'length' => 13,
'expected' => 'echo \'foo\',
\'bar\' ,
\'baz\';',
],
'end-of-file' => [
'testMarker' => '/* testEndOfFile */',
'startTokenType' => T_ECHO,
'length' => 4,
'expected' => 'echo $foo;',
],
];
}//end dataGetOrigContent()
}//end class

View File

@@ -0,0 +1,216 @@
<?php
/* testTokenizerIssue1971PHPCSlt330gt271A */
// This has to be the first test in the file!
[&$a, [$b, /* testTokenizerIssue1971PHPCSlt330gt271B */ &$c]] = $array;
/* testBitwiseAndA */
error_reporting( E_NOTICE & E_STRICT );
/* testBitwiseAndB */
$a = [ $something & $somethingElse ];
/* testBitwiseAndC */
$a = [ $first, $something & self::$somethingElse ];
/* testBitwiseAndD */
$a = array( $first, $something & $somethingElse );
/* testBitwiseAndE */
$a = [ 'a' => $first, 'b' => $something & $somethingElse ];
/* testBitwiseAndF */
$a = array( 'a' => $first, 'b' => $something & \MyClass::$somethingElse );
/* testBitwiseAndG */
$a = $something & $somethingElse;
/* testBitwiseAndH */
function myFunction($a = 10 & 20) {}
/* testBitwiseAndI */
$closure = function ($a = MY_CONSTANT & parent::OTHER_CONSTANT) {};
/* testFunctionReturnByReference */
function &myFunction() {}
/* testFunctionPassByReferenceA */
function myFunction( &$a ) {}
/* testFunctionPassByReferenceB */
function myFunction( $a, &$b ) {}
/* testFunctionPassByReferenceC */
$closure = function ( &$a ) {};
/* testFunctionPassByReferenceD */
$closure = function ( $a, &$b ) {};
/* testFunctionPassByReferenceE */
function myFunction(array &$one) {}
/* testFunctionPassByReferenceF */
$closure = function (\MyClass &$one) {};
/* testFunctionPassByReferenceG */
$closure = function ($param, &...$moreParams) {};
/* testForeachValueByReference */
foreach( $array as $key => &$value ) {}
/* testForeachKeyByReference */
foreach( $array as &$key => $value ) {}
/* testArrayValueByReferenceA */
$a = [ 'a' => &$something ];
/* testArrayValueByReferenceB */
$a = [ 'a' => $something, 'b' => &$somethingElse ];
/* testArrayValueByReferenceC */
$a = [ &$something ];
/* testArrayValueByReferenceD */
$a = [ $something, &$somethingElse ];
/* testArrayValueByReferenceE */
$a = array( 'a' => &$something );
/* testArrayValueByReferenceF */
$a = array( 'a' => $something, 'b' => &$somethingElse );
/* testArrayValueByReferenceG */
$a = array( &$something );
/* testArrayValueByReferenceH */
$a = array( $something, &$somethingElse );
/* testAssignByReferenceA */
$b = &$something;
/* testAssignByReferenceB */
$b =& $something;
/* testAssignByReferenceC */
$b .= &$something;
/* testAssignByReferenceD */
$myValue = &$obj->getValue();
/* testAssignByReferenceE */
$collection = &collector();
/* testAssignByReferenceF */
$collection ??= &collector();
/* testShortListAssignByReferenceNoKeyA */
[
&$a,
/* testShortListAssignByReferenceNoKeyB */
&$b,
/* testNestedShortListAssignByReferenceNoKey */
[$c, &$d]
] = $array;
/* testLongListAssignByReferenceNoKeyA */
list($a, &$b, list(/* testLongListAssignByReferenceNoKeyB */ &$c, /* testLongListAssignByReferenceNoKeyC */ &$d)) = $array;
[
/* testNestedShortListAssignByReferenceWithKeyA */
'a' => [&$a, $b],
/* testNestedShortListAssignByReferenceWithKeyB */
'b' => [$c, &$d]
] = $array;
/* testLongListAssignByReferenceWithKeyA */
list(get_key()[1] => &$e) = [1, 2, 3];
/* 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);
/* testPassByReferencePartiallyQualifiedName */
functionCall($something, &Sub\Level\SomeClass::$somethingElse);
/* testNewByReferenceA */
$foobar2 = &new Foobar();
/* testNewByReferenceB */
functionCall( $something , &new Foobar() );
/* testUseByReference */
$closure = function() use (&$var){};
/* testUseByReferenceWithCommentFirstParam */
$closure = function() use /*comment*/ (&$value){};
/* testUseByReferenceWithCommentSecondParam */
$closure = function() use /*comment*/ ($varA, &$varB){};
/* testArrowFunctionReturnByReference */
fn&($x) => $x;
$closure = function (
/* testBitwiseAndExactParameterA */
$a = MY_CONSTANT & parent::OTHER_CONSTANT,
/* testPassByReferenceExactParameterB */
&$b,
/* testPassByReferenceExactParameterC */
&...$c,
/* testBitwiseAndExactParameterD */
$d = E_NOTICE & E_STRICT,
) {};
// Issue PHPCS#3049.
/* testArrowFunctionPassByReferenceA */
$fn = fn(array &$one) => 1;
/* testArrowFunctionPassByReferenceB */
$fn = fn($param, &...$moreParams) => 1;
/* testClosureReturnByReference */
$closure = function &($param) use ($value) {};
/* testBitwiseAndArrowFunctionInDefault */
$fn = fn( $one = E_NOTICE & E_STRICT) => 1;
/* testIntersectionIsNotReference */
function intersect(Foo&Bar $param) {}
/* testDNFTypeIsNotReference */
$fn = fn((Foo&\Bar)|null /* testParamPassByReference */ &$param) => $param;
/* testTokenizerIssue1284PHPCSlt280A */
if ($foo) {}
[&$a, /* testTokenizerIssue1284PHPCSlt280B */ &$b] = $c;
/* testTokenizerIssue1284PHPCSlt280C */
if ($foo) {}
[&$a, $b];

View File

@@ -0,0 +1,396 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Files\File;
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
/**
* Tests for the \PHP_CodeSniffer\Files\File::isReference method.
*
* @covers \PHP_CodeSniffer\Files\File::isReference
*/
final class IsReferenceTest extends AbstractMethodUnitTest
{
/**
* Test that false is returned when a non-"bitwise and" token is passed.
*
* @param string $testMarker Comment which precedes the test case.
* @param array<int|string> $targetTokens Type of tokens to look for.
*
* @dataProvider dataNotBitwiseAndToken
*
* @return void
*/
public function testNotBitwiseAndToken($testMarker, $targetTokens)
{
$targetTokens[] = T_BITWISE_AND;
$target = $this->getTargetToken($testMarker, $targetTokens);
$this->assertFalse(self::$phpcsFile->isReference($target));
}//end testNotBitwiseAndToken()
/**
* Data provider.
*
* @see testNotBitwiseAndToken()
*
* @return array<string, array<string, string|array<int|string>>>
*/
public static function dataNotBitwiseAndToken()
{
return [
'Not ampersand token at all' => [
'testMarker' => '/* testBitwiseAndA */',
'targetTokens' => [T_STRING],
],
'ampersand in intersection type' => [
'testMarker' => '/* testIntersectionIsNotReference */',
'targetTokens' => [T_TYPE_INTERSECTION],
],
'ampersand in DNF type' => [
'testMarker' => '/* testDNFTypeIsNotReference */',
'targetTokens' => [T_TYPE_INTERSECTION],
],
];
}//end dataNotBitwiseAndToken()
/**
* Test correctly identifying whether a "bitwise and" token is a reference or not.
*
* @param string $testMarker Comment which precedes the test case.
* @param bool $expected Expected function output.
*
* @dataProvider dataIsReference
*
* @return void
*/
public function testIsReference($testMarker, $expected)
{
$bitwiseAnd = $this->getTargetToken($testMarker, T_BITWISE_AND);
$result = self::$phpcsFile->isReference($bitwiseAnd);
$this->assertSame($expected, $result);
}//end testIsReference()
/**
* Data provider for the IsReference test.
*
* @see testIsReference()
*
* @return array<string, array<string, string|bool>>
*/
public static function dataIsReference()
{
return [
'issue-1971-list-first-in-file' => [
'testMarker' => '/* testTokenizerIssue1971PHPCSlt330gt271A */',
'expected' => true,
],
'issue-1971-list-first-in-file-nested' => [
'testMarker' => '/* testTokenizerIssue1971PHPCSlt330gt271B */',
'expected' => true,
],
'bitwise and: param in function call' => [
'testMarker' => '/* testBitwiseAndA */',
'expected' => false,
],
'bitwise and: in unkeyed short array, first value' => [
'testMarker' => '/* testBitwiseAndB */',
'expected' => false,
],
'bitwise and: in unkeyed short array, last value' => [
'testMarker' => '/* testBitwiseAndC */',
'expected' => false,
],
'bitwise and: in unkeyed long array, last value' => [
'testMarker' => '/* testBitwiseAndD */',
'expected' => false,
],
'bitwise and: in keyed short array, last value' => [
'testMarker' => '/* testBitwiseAndE */',
'expected' => false,
],
'bitwise and: in keyed long array, last value' => [
'testMarker' => '/* testBitwiseAndF */',
'expected' => false,
],
'bitwise and: in assignment' => [
'testMarker' => '/* testBitwiseAndG */',
'expected' => false,
],
'bitwise and: in param default value in function declaration' => [
'testMarker' => '/* testBitwiseAndH */',
'expected' => false,
],
'bitwise and: in param default value in closure declaration' => [
'testMarker' => '/* testBitwiseAndI */',
'expected' => false,
],
'reference: function declared to return by reference' => [
'testMarker' => '/* testFunctionReturnByReference */',
'expected' => true,
],
'reference: only param in function declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceA */',
'expected' => true,
],
'reference: last param in function declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceB */',
'expected' => true,
],
'reference: only param in closure declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceC */',
'expected' => true,
],
'reference: last param in closure declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceD */',
'expected' => true,
],
'reference: typed param in function declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceE */',
'expected' => true,
],
'reference: typed param in closure declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceF */',
'expected' => true,
],
'reference: variadic param in function declaration, pass by reference' => [
'testMarker' => '/* testFunctionPassByReferenceG */',
'expected' => true,
],
'reference: foreach value' => [
'testMarker' => '/* testForeachValueByReference */',
'expected' => true,
],
'reference: foreach key' => [
'testMarker' => '/* testForeachKeyByReference */',
'expected' => true,
],
'reference: keyed short array, first value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceA */',
'expected' => true,
],
'reference: keyed short array, last value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceB */',
'expected' => true,
],
'reference: unkeyed short array, only value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceC */',
'expected' => true,
],
'reference: unkeyed short array, last value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceD */',
'expected' => true,
],
'reference: keyed long array, first value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceE */',
'expected' => true,
],
'reference: keyed long array, last value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceF */',
'expected' => true,
],
'reference: unkeyed long array, only value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceG */',
'expected' => true,
],
'reference: unkeyed long array, last value, value by reference' => [
'testMarker' => '/* testArrayValueByReferenceH */',
'expected' => true,
],
'reference: variable, assign by reference' => [
'testMarker' => '/* testAssignByReferenceA */',
'expected' => true,
],
'reference: variable, assign by reference, spacing variation' => [
'testMarker' => '/* testAssignByReferenceB */',
'expected' => true,
],
'reference: variable, assign by reference, concat assign' => [
'testMarker' => '/* testAssignByReferenceC */',
'expected' => true,
],
'reference: property, assign by reference' => [
'testMarker' => '/* testAssignByReferenceD */',
'expected' => true,
],
'reference: function return value, assign by reference' => [
'testMarker' => '/* testAssignByReferenceE */',
'expected' => true,
],
'reference: function return value, assign by reference, null coalesce assign' => [
'testMarker' => '/* testAssignByReferenceF */',
'expected' => true,
],
'reference: unkeyed short list, first var, assign by reference' => [
'testMarker' => '/* testShortListAssignByReferenceNoKeyA */',
'expected' => true,
],
'reference: unkeyed short list, second var, assign by reference' => [
'testMarker' => '/* testShortListAssignByReferenceNoKeyB */',
'expected' => true,
],
'reference: unkeyed short list, nested var, assign by reference' => [
'testMarker' => '/* testNestedShortListAssignByReferenceNoKey */',
'expected' => true,
],
'reference: unkeyed long list, second var, assign by reference' => [
'testMarker' => '/* testLongListAssignByReferenceNoKeyA */',
'expected' => true,
],
'reference: unkeyed long list, first nested var, assign by reference' => [
'testMarker' => '/* testLongListAssignByReferenceNoKeyB */',
'expected' => true,
],
'reference: unkeyed long list, last nested var, assign by reference' => [
'testMarker' => '/* testLongListAssignByReferenceNoKeyC */',
'expected' => true,
],
'reference: keyed short list, first nested var, assign by reference' => [
'testMarker' => '/* testNestedShortListAssignByReferenceWithKeyA */',
'expected' => true,
],
'reference: keyed short list, last nested var, assign by reference' => [
'testMarker' => '/* testNestedShortListAssignByReferenceWithKeyB */',
'expected' => true,
],
'reference: keyed long list, only var, assign by reference' => [
'testMarker' => '/* testLongListAssignByReferenceWithKeyA */',
'expected' => true,
],
'reference: first param in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceA */',
'expected' => true,
],
'reference: last param in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceB */',
'expected' => true,
],
'reference: property in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceC */',
'expected' => true,
],
'reference: hierarchical self property in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceD */',
'expected' => true,
],
'reference: hierarchical parent property in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceE */',
'expected' => true,
],
'reference: hierarchical static property in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceF */',
'expected' => true,
],
'reference: static property in function call, pass by reference' => [
'testMarker' => '/* testPassByReferenceG */',
'expected' => true,
],
'reference: static property in function call, first with FQN, pass by reference' => [
'testMarker' => '/* testPassByReferenceH */',
'expected' => true,
],
'reference: static property in function call, last with FQN, pass by reference' => [
'testMarker' => '/* testPassByReferenceI */',
'expected' => true,
],
'reference: static property in function call, last with namespace relative name, pass by reference' => [
'testMarker' => '/* testPassByReferenceJ */',
'expected' => true,
],
'reference: static property in function call, last with PQN, pass by reference' => [
'testMarker' => '/* testPassByReferencePartiallyQualifiedName */',
'expected' => true,
],
'reference: new by reference' => [
'testMarker' => '/* testNewByReferenceA */',
'expected' => true,
],
'reference: new by reference as function call param' => [
'testMarker' => '/* testNewByReferenceB */',
'expected' => true,
],
'reference: closure use by reference' => [
'testMarker' => '/* testUseByReference */',
'expected' => true,
],
'reference: closure use by reference, first param, with comment' => [
'testMarker' => '/* testUseByReferenceWithCommentFirstParam */',
'expected' => true,
],
'reference: closure use by reference, last param, with comment' => [
'testMarker' => '/* testUseByReferenceWithCommentSecondParam */',
'expected' => true,
],
'reference: arrow fn declared to return by reference' => [
'testMarker' => '/* testArrowFunctionReturnByReference */',
'expected' => true,
],
'bitwise and: first param default value in closure declaration' => [
'testMarker' => '/* testBitwiseAndExactParameterA */',
'expected' => false,
],
'reference: param in closure declaration, pass by reference' => [
'testMarker' => '/* testPassByReferenceExactParameterB */',
'expected' => true,
],
'reference: variadic param in closure declaration, pass by reference' => [
'testMarker' => '/* testPassByReferenceExactParameterC */',
'expected' => true,
],
'bitwise and: last param default value in closure declaration' => [
'testMarker' => '/* testBitwiseAndExactParameterD */',
'expected' => false,
],
'reference: typed param in arrow fn declaration, pass by reference' => [
'testMarker' => '/* testArrowFunctionPassByReferenceA */',
'expected' => true,
],
'reference: variadic param in arrow fn declaration, pass by reference' => [
'testMarker' => '/* testArrowFunctionPassByReferenceB */',
'expected' => true,
],
'reference: closure declared to return by reference' => [
'testMarker' => '/* testClosureReturnByReference */',
'expected' => true,
],
'bitwise and: param default value in arrow fn declaration' => [
'testMarker' => '/* testBitwiseAndArrowFunctionInDefault */',
'expected' => false,
],
'reference: param pass by ref in arrow function' => [
'testMarker' => '/* testParamPassByReference */',
'expected' => true,
],
'issue-1284-short-list-directly-after-close-curly-control-structure' => [
'testMarker' => '/* testTokenizerIssue1284PHPCSlt280A */',
'expected' => true,
],
'issue-1284-short-list-directly-after-close-curly-control-structure-second-item' => [
'testMarker' => '/* testTokenizerIssue1284PHPCSlt280B */',
'expected' => true,
],
'issue-1284-short-array-directly-after-close-curly-control-structure' => [
'testMarker' => '/* testTokenizerIssue1284PHPCSlt280C */',
'expected' => true,
],
];
}//end dataIsReference()
}//end class

View File

@@ -0,0 +1,250 @@
<?php
/**
* Abstract Testcase class for testing Filters.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2023 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Filters;
use PHP_CodeSniffer\Filters\Filter;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
use RecursiveIteratorIterator;
/**
* Base functionality and utilities for testing Filter classes.
*/
abstract class AbstractFilterTestCase extends TestCase
{
/**
* The Config object.
*
* @var \PHP_CodeSniffer\Config
*/
protected static $config;
/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected static $ruleset;
/**
* Initialize the config and ruleset objects.
*
* @beforeClass
*
* @return void
*/
public static function initializeConfigAndRuleset()
{
self::$config = new ConfigDouble(['--extensions=php,inc/php,js,css']);
self::$ruleset = new Ruleset(self::$config);
}//end initializeConfigAndRuleset()
/**
* Clean up after finished test by resetting all static properties on the Config class to their default values.
*
* Note: This is a PHPUnit cross-version compatible {@see \PHPUnit\Framework\TestCase::tearDownAfterClass()}
* method.
*
* @afterClass
*
* @return void
*/
public static function reset()
{
// Explicitly trigger __destruct() on the ConfigDouble to reset the Config statics.
// The explicit method call prevents potential stray test-local references to the $config object
// preventing the destructor from running the clean up (which without stray references would be
// automagically triggered when `self::$phpcsFile` is reset, but we can't definitively rely on that).
if (isset(self::$config) === true) {
self::$config->__destruct();
}
}//end reset()
/**
* Helper method to retrieve a mock object for a Filter class.
*
* The `setMethods()` method was silently deprecated in PHPUnit 9 and removed in PHPUnit 10.
*
* Note: direct access to the `getMockBuilder()` method is soft deprecated as of PHPUnit 10,
* and expected to be hard deprecated in PHPUnit 11 and removed in PHPUnit 12.
* Dealing with that is something for a later iteration of the test suite.
*
* @param string $className Fully qualified name of the class under test.
* @param array<mixed> $constructorArgs Optional. Array of parameters to pass to the class constructor.
* @param array<string>|null $methodsToMock Optional. The methods to mock in the class under test.
* Needed for PHPUnit cross-version support as PHPUnit 4.x does
* not have a `setMethodsExcept()` method yet.
* If not passed, no methods will be replaced.
*
* @return \PHPUnit\Framework\MockObject\MockObject
*/
protected function getMockedClass($className, array $constructorArgs=[], $methodsToMock=null)
{
$mockedObj = $this->getMockBuilder($className);
if (method_exists($mockedObj, 'onlyMethods') === true) {
// PHPUnit 8+.
if (is_array($methodsToMock) === true) {
return $mockedObj
->setConstructorArgs($constructorArgs)
->onlyMethods($methodsToMock)
->getMock();
}
return $mockedObj->getMock()
->setConstructorArgs($constructorArgs);
}
// PHPUnit < 8.
return $mockedObj
->setConstructorArgs($constructorArgs)
->setMethods($methodsToMock)
->getMock();
}//end getMockedClass()
/**
* Retrieve an array of files which were accepted by a filter.
*
* @param \PHP_CodeSniffer\Filters\Filter $filter The Filter object under test.
*
* @return array<string>
*/
protected function getFilteredResultsAsArray(Filter $filter)
{
$iterator = new RecursiveIteratorIterator($filter);
$files = [];
foreach ($iterator as $file) {
$files[] = $file;
}
return $files;
}//end getFilteredResultsAsArray()
/**
* Retrieve the basedir to use for tests using the `getFakeFileList()` method.
*
* @return string
*/
protected static function getBaseDir()
{
return dirname(dirname(dirname(__DIR__)));
}//end getBaseDir()
/**
* Retrieve a file list containing a range of paths for testing purposes.
*
* This list **must** contain files which exist in this project (well, except for some which don't exist
* purely for testing purposes), as `realpath()` is used in the logic under test and `realpath()` will
* return `false` for any non-existent files, which will automatically filter them out before
* we get to the code under test.
*
* Note this list does not include `.` and `..` as \PHP_CodeSniffer\Files\FileList uses `SKIP_DOTS`.
*
* @return array<string>
*/
protected static function getFakeFileList()
{
$basedir = self::getBaseDir();
return [
$basedir.'/.gitignore',
$basedir.'/.yamllint.yml',
$basedir.'/phpcs.xml',
$basedir.'/phpcs.xml.dist',
$basedir.'/autoload.php',
$basedir.'/bin',
$basedir.'/bin/phpcs',
$basedir.'/bin/phpcs.bat',
$basedir.'/scripts',
$basedir.'/scripts/build-phar.php',
$basedir.'/src',
$basedir.'/src/WillNotExist.php',
$basedir.'/src/WillNotExist.bak',
$basedir.'/src/WillNotExist.orig',
$basedir.'/src/Ruleset.php',
$basedir.'/src/Generators',
$basedir.'/src/Generators/Markdown.php',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Generic',
$basedir.'/src/Standards/Generic/Docs',
$basedir.'/src/Standards/Generic/Docs/Classes',
$basedir.'/src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml',
$basedir.'/src/Standards/Generic/Sniffs',
$basedir.'/src/Standards/Generic/Sniffs/Classes',
$basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
$basedir.'/src/Standards/Generic/Tests',
$basedir.'/src/Standards/Generic/Tests/Classes',
$basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc',
// Will rarely exist when running the tests.
$basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.1.inc.bak',
$basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.2.inc',
$basedir.'/src/Standards/Generic/Tests/Classes/DuplicateClassNameUnitTest.php',
$basedir.'/src/Standards/Squiz',
$basedir.'/src/Standards/Squiz/Docs',
$basedir.'/src/Standards/Squiz/Docs/WhiteSpace',
$basedir.'/src/Standards/Squiz/Docs/WhiteSpace/SemicolonSpacingStandard.xml',
$basedir.'/src/Standards/Squiz/Sniffs',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php',
$basedir.'/src/Standards/Squiz/Tests',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc.fixed',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php',
];
}//end getFakeFileList()
/**
* Translate Linux paths to Windows paths, when necessary.
*
* These type of tests should be able to run and pass on both *nix as well as Windows
* based dev systems. This method is a helper to allow for this.
*
* @param array<string|array<string>> $paths A single or multi-dimensional array containing
* file paths.
*
* @return array<string|array<string>>
*/
protected static function mapPathsToRuntimeOs(array $paths)
{
if (DIRECTORY_SEPARATOR !== '\\') {
return $paths;
}
foreach ($paths as $key => $value) {
if (is_string($value) === true) {
$paths[$key] = strtr($value, '/', '\\\\');
} else if (is_array($value) === true) {
$paths[$key] = self::mapPathsToRuntimeOs($value);
}
}
return $paths;
}//end mapPathsToRuntimeOs()
}//end class

View File

@@ -0,0 +1,110 @@
<?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/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Filters\Filter;
use PHP_CodeSniffer\Filters\Filter;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase;
use RecursiveArrayIterator;
/**
* Tests for the \PHP_CodeSniffer\Filters\Filter::accept method.
*
* @covers \PHP_CodeSniffer\Filters\Filter
*/
final class AcceptTest extends AbstractFilterTestCase
{
/**
* Initialize the config and ruleset objects based on the `AcceptTest.xml` ruleset file.
*
* @beforeClass
*
* @return void
*/
public static function initializeConfigAndRuleset()
{
$standard = __DIR__.'/'.basename(__FILE__, '.php').'.xml';
self::$config = new ConfigDouble(["--standard=$standard", '--ignore=*/somethingelse/*']);
self::$ruleset = new Ruleset(self::$config);
}//end initializeConfigAndRuleset()
/**
* Test filtering a file list for excluded paths.
*
* @param array<string> $inputPaths List of file paths to be filtered.
* @param array<string> $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);
$this->assertSame($expectedOutput, $this->getFilteredResultsAsArray($filter));
}//end testExcludePatterns()
/**
* Data provider.
*
* @see testExcludePatterns
*
* @return array<string, array<string, array<string>>>
*/
public static function dataExcludePatterns()
{
$testCases = [
// Test top-level exclude patterns.
'Non-sniff specific path based excludes from ruleset and command line are respected and don\'t filter out too much' => [
'inputPaths' => [
'/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',
],
'expectedOutput' => [
'/path/to/src/Main.php',
'/path/to/src/SomethingelseEvenLonger/Main.php',
],
],
// Test ignoring standard/sniff specific exclude patterns.
'Filter should not act on standard/sniff specific exclude patterns' => [
'inputPaths' => [
'/path/to/src/generic-project/Main.php',
'/path/to/src/generic/Main.php',
'/path/to/src/anything-generic/Main.php',
],
'expectedOutput' => [
'/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.
return self::mapPathsToRuntimeOs($testCases);
}//end dataExcludePatterns()
}//end class

View File

@@ -0,0 +1,18 @@
<?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">
<exclude name="Generic.Debug"/>
<!-- Standard specific directory pattern. -->
<exclude-pattern>/anything/*</exclude-pattern>
<!-- Standard specific file pattern. -->
<exclude-pattern>/YetAnother/Main\.php</exclude-pattern>
</rule>
</ruleset>

View File

@@ -0,0 +1,273 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Filters\GitModified class.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2023 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Filters;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Filters\GitModified;
use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase;
use RecursiveArrayIterator;
use ReflectionMethod;
/**
* Tests for the \PHP_CodeSniffer\Filters\GitModified class.
*
* @covers \PHP_CodeSniffer\Filters\GitModified
*/
final class GitModifiedTest extends AbstractFilterTestCase
{
/**
* Test filtering a file list for excluded paths.
*
* @return void
*/
public function testFileNamePassesAsBasePathWillTranslateToDirname()
{
$rootFile = self::getBaseDir().'/autoload.php';
$fakeDI = new RecursiveArrayIterator(self::getFakeFileList());
$constructorArgs = [
$fakeDI,
$rootFile,
self::$config,
self::$ruleset,
];
$mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitModified', $constructorArgs, ['exec']);
$mockObj->expects($this->once())
->method('exec')
->willReturn(['autoload.php']);
$this->assertSame([$rootFile], $this->getFilteredResultsAsArray($mockObj));
}//end testFileNamePassesAsBasePathWillTranslateToDirname()
/**
* Test filtering a file list for excluded paths.
*
* @param array<string> $inputPaths List of file paths to be filtered.
* @param array<string> $outputGitModified Simulated "git modified" output.
* @param array<string> $expectedOutput Expected filtering result.
*
* @dataProvider dataAcceptOnlyGitModified
*
* @return void
*/
public function testAcceptOnlyGitModified($inputPaths, $outputGitModified, $expectedOutput)
{
$fakeDI = new RecursiveArrayIterator($inputPaths);
$constructorArgs = [
$fakeDI,
self::getBaseDir(),
self::$config,
self::$ruleset,
];
$mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitModified', $constructorArgs, ['exec']);
$mockObj->expects($this->once())
->method('exec')
->willReturn($outputGitModified);
$this->assertSame($expectedOutput, $this->getFilteredResultsAsArray($mockObj));
}//end testAcceptOnlyGitModified()
/**
* Data provider.
*
* @see testAcceptOnlyGitModified
*
* @return array<string, array<string, array<string>>>
*/
public static function dataAcceptOnlyGitModified()
{
$basedir = self::getBaseDir();
$fakeFileList = self::getFakeFileList();
$testCases = [
'no files marked as git modified' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [],
'expectedOutput' => [],
],
'files marked as git modified which don\'t actually exist' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'src/WillNotExist.php',
'src/WillNotExist.bak',
'src/WillNotExist.orig',
],
'expectedOutput' => [],
],
'single file marked as git modified - file in root dir' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'autoload.php',
],
'expectedOutput' => [
$basedir.'/autoload.php',
],
],
'single file marked as git modified - file in sub dir' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Generic',
$basedir.'/src/Standards/Generic/Sniffs',
$basedir.'/src/Standards/Generic/Sniffs/Classes',
$basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
],
'multiple files marked as git modified, none valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'.gitignore',
'phpcs.xml.dist',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Squiz',
$basedir.'/src/Standards/Squiz/Tests',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace',
],
],
'multiple files marked as git modified, only one file valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'.gitignore',
'src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml',
'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Generic',
$basedir.'/src/Standards/Generic/Docs',
$basedir.'/src/Standards/Generic/Docs/Classes',
$basedir.'/src/Standards/Generic/Sniffs',
$basedir.'/src/Standards/Generic/Sniffs/Classes',
$basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
],
'multiple files marked as git modified, multiple files valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitModified' => [
'.yamllint.yml',
'autoload.php',
'src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc.fixed',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php',
],
'expectedOutput' => [
$basedir.'/autoload.php',
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Squiz',
$basedir.'/src/Standards/Squiz/Sniffs',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php',
$basedir.'/src/Standards/Squiz/Tests',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php',
],
],
];
return $testCases;
}//end dataAcceptOnlyGitModified()
/**
* Test filtering a file list for excluded paths.
*
* @param string $cmd Command to run.
* @param array<string> $expected Expected return value.
*
* @dataProvider dataExecAlwaysReturnsArray
*
* @return void
*/
public function testExecAlwaysReturnsArray($cmd, $expected)
{
if (is_dir(__DIR__.'/../../../.git') === false) {
$this->markTestSkipped('Not a git repository');
}
if (Config::getExecutablePath('git') === null) {
$this->markTestSkipped('git command not available');
}
$fakeDI = new RecursiveArrayIterator(self::getFakeFileList());
$filter = new GitModified($fakeDI, '/', self::$config, self::$ruleset);
$reflMethod = new ReflectionMethod($filter, 'exec');
(PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke($filter, $cmd);
$this->assertSame($expected, $result);
}//end testExecAlwaysReturnsArray()
/**
* Data provider.
*
* @see testExecAlwaysReturnsArray
*
* {@internal Missing: test with a command which yields a `false` return value.
* JRF: I've not managed to find a command which does so, let alone one, which then
* doesn't have side-effects of uncatchable output while running the tests.}
*
* @return array<string, array<string, string|array<string>>>
*/
public static function dataExecAlwaysReturnsArray()
{
return [
'valid command which won\'t have any output unless files in the bin dir have been modified' => [
// Largely using the command used in the filter, but only checking the bin dir.
// This should prevent the test unexpectedly failing during local development (in most cases).
'cmd' => 'git ls-files -o -m --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'),
'expected' => [],
],
'valid command which will have output' => [
'cmd' => 'git ls-files --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'),
'expected' => [
'bin/phpcbf',
'bin/phpcbf.bat',
'bin/phpcs',
'bin/phpcs.bat',
],
],
];
}//end dataExecAlwaysReturnsArray()
}//end class

View File

@@ -0,0 +1,273 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Filters\GitStaged class.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2023 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Filters;
use PHP_CodeSniffer\Config;
use PHP_CodeSniffer\Filters\GitStaged;
use PHP_CodeSniffer\Tests\Core\Filters\AbstractFilterTestCase;
use RecursiveArrayIterator;
use ReflectionMethod;
/**
* Tests for the \PHP_CodeSniffer\Filters\GitStaged class.
*
* @covers \PHP_CodeSniffer\Filters\GitStaged
*/
final class GitStagedTest extends AbstractFilterTestCase
{
/**
* Test filtering a file list for excluded paths.
*
* @return void
*/
public function testFileNamePassesAsBasePathWillTranslateToDirname()
{
$rootFile = self::getBaseDir().'/autoload.php';
$fakeDI = new RecursiveArrayIterator(self::getFakeFileList());
$constructorArgs = [
$fakeDI,
$rootFile,
self::$config,
self::$ruleset,
];
$mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitStaged', $constructorArgs, ['exec']);
$mockObj->expects($this->once())
->method('exec')
->willReturn(['autoload.php']);
$this->assertSame([$rootFile], $this->getFilteredResultsAsArray($mockObj));
}//end testFileNamePassesAsBasePathWillTranslateToDirname()
/**
* Test filtering a file list for excluded paths.
*
* @param array<string> $inputPaths List of file paths to be filtered.
* @param array<string> $outputGitStaged Simulated "git staged" output.
* @param array<string> $expectedOutput Expected filtering result.
*
* @dataProvider dataAcceptOnlyGitStaged
*
* @return void
*/
public function testAcceptOnlyGitStaged($inputPaths, $outputGitStaged, $expectedOutput)
{
$fakeDI = new RecursiveArrayIterator($inputPaths);
$constructorArgs = [
$fakeDI,
self::getBaseDir(),
self::$config,
self::$ruleset,
];
$mockObj = $this->getMockedClass('PHP_CodeSniffer\Filters\GitStaged', $constructorArgs, ['exec']);
$mockObj->expects($this->once())
->method('exec')
->willReturn($outputGitStaged);
$this->assertSame($expectedOutput, $this->getFilteredResultsAsArray($mockObj));
}//end testAcceptOnlyGitStaged()
/**
* Data provider.
*
* @see testAcceptOnlyGitStaged
*
* @return array<string, array<string, array<string>>>
*/
public static function dataAcceptOnlyGitStaged()
{
$basedir = self::getBaseDir();
$fakeFileList = self::getFakeFileList();
$testCases = [
'no files marked as git modified' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [],
'expectedOutput' => [],
],
'files marked as git modified which don\'t actually exist' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'src/WillNotExist.php',
'src/WillNotExist.bak',
'src/WillNotExist.orig',
],
'expectedOutput' => [],
],
'single file marked as git modified - file in root dir' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'autoload.php',
],
'expectedOutput' => [
$basedir.'/autoload.php',
],
],
'single file marked as git modified - file in sub dir' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Generic',
$basedir.'/src/Standards/Generic/Sniffs',
$basedir.'/src/Standards/Generic/Sniffs/Classes',
$basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
],
'multiple files marked as git modified, none valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'.gitignore',
'phpcs.xml.dist',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Squiz',
$basedir.'/src/Standards/Squiz/Tests',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace',
],
],
'multiple files marked as git modified, only one file valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'.gitignore',
'src/Standards/Generic/Docs/Classes/DuplicateClassNameStandard.xml',
'src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
'expectedOutput' => [
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Generic',
$basedir.'/src/Standards/Generic/Docs',
$basedir.'/src/Standards/Generic/Docs/Classes',
$basedir.'/src/Standards/Generic/Sniffs',
$basedir.'/src/Standards/Generic/Sniffs/Classes',
$basedir.'/src/Standards/Generic/Sniffs/Classes/DuplicateClassNameSniff.php',
],
],
'multiple files marked as git modified, multiple files valid for scan' => [
'inputPaths' => $fakeFileList,
'outputGitStaged' => [
'.yamllint.yml',
'autoload.php',
'src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc.fixed',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js.fixed',
'src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php',
],
'expectedOutput' => [
$basedir.'/autoload.php',
$basedir.'/src',
$basedir.'/src/Standards',
$basedir.'/src/Standards/Squiz',
$basedir.'/src/Standards/Squiz/Sniffs',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace',
$basedir.'/src/Standards/Squiz/Sniffs/WhiteSpace/OperatorSpacingSniff.php',
$basedir.'/src/Standards/Squiz/Tests',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.1.inc',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.js',
$basedir.'/src/Standards/Squiz/Tests/WhiteSpace/OperatorSpacingUnitTest.php',
],
],
];
return $testCases;
}//end dataAcceptOnlyGitStaged()
/**
* Test filtering a file list for excluded paths.
*
* @param string $cmd Command to run.
* @param array<string> $expected Expected return value.
*
* @dataProvider dataExecAlwaysReturnsArray
*
* @return void
*/
public function testExecAlwaysReturnsArray($cmd, $expected)
{
if (is_dir(__DIR__.'/../../../.git') === false) {
$this->markTestSkipped('Not a git repository');
}
if (Config::getExecutablePath('git') === null) {
$this->markTestSkipped('git command not available');
}
$fakeDI = new RecursiveArrayIterator(self::getFakeFileList());
$filter = new GitStaged($fakeDI, '/', self::$config, self::$ruleset);
$reflMethod = new ReflectionMethod($filter, 'exec');
(PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke($filter, $cmd);
$this->assertSame($expected, $result);
}//end testExecAlwaysReturnsArray()
/**
* Data provider.
*
* @see testExecAlwaysReturnsArray
*
* {@internal Missing: test with a command which yields a `false` return value.
* JRF: I've not managed to find a command which does so, let alone one, which then
* doesn't have side-effects of uncatchable output while running the tests.}
*
* @return array<string, array<string, string|array<string>>>
*/
public static function dataExecAlwaysReturnsArray()
{
return [
'valid command which won\'t have any output unless files in the bin dir have been modified & staged' => [
// Largely using the command used in the filter, but only checking the bin dir.
// This should prevent the test unexpectedly failing during local development (in most cases).
'cmd' => 'git diff --cached --name-only -- '.escapeshellarg(self::getBaseDir().'/bin'),
'expected' => [],
],
'valid command which will have output' => [
'cmd' => 'git ls-files --exclude-standard -- '.escapeshellarg(self::getBaseDir().'/bin'),
'expected' => [
'bin/phpcbf',
'bin/phpcbf.bat',
'bin/phpcs',
'bin/phpcs.bat',
],
],
];
}//end dataExecAlwaysReturnsArray()
}//end class

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="FixFileReturnValueTest" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
<config name="installed_paths" value="./tests/Core/Fixer/Fixtures/TestStandard/"/>
<rule ref="TestStandard.FixFileReturnValue.AllGood"/>
</ruleset>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="FixFileReturnValueTest" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
<config name="installed_paths" value="./tests/Core/Fixer/Fixtures/TestStandard/"/>
<rule ref="TestStandard.FixFileReturnValue.Conflict"/>
</ruleset>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="FixFileReturnValueTest" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
<config name="installed_paths" value="./tests/Core/Fixer/Fixtures/TestStandard/"/>
<rule ref="TestStandard.FixFileReturnValue.NotEnoughLoops"/>
</ruleset>

View File

@@ -0,0 +1,89 @@
<?php
/**
* Tests for the Fixer::fixFile() return value.
*
* @copyright 2025 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Fixer;
use PHP_CodeSniffer\Files\LocalFile;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for the Fixer::fixFile() return value.
*
* @covers PHP_CodeSniffer\Fixer::fixFile
*/
final class FixFileReturnValueTest extends TestCase
{
/**
* Test that the return value of the fixFile() method is true when the file was completely fixed.
*
* @return void
*/
public function testReturnValueIsTrueWhenFileWasFixed()
{
$standard = __DIR__.'/FixFileReturnValueAllGoodTest.xml';
$config = new ConfigDouble(["--standard=$standard"]);
$ruleset = new Ruleset($config);
$testCaseFile = __DIR__.'/Fixtures/test.inc';
$phpcsFile = new LocalFile($testCaseFile, $ruleset, $config);
$phpcsFile->process();
$fixed = $phpcsFile->fixer->fixFile();
$this->assertTrue($fixed);
}//end testReturnValueIsTrueWhenFileWasFixed()
/**
* Test that the return value of the fixFile() method is false when the file failed to make all fixes.
*
* @param string $standard The ruleset file to use for the test.
*
* @dataProvider dataReturnValueIsFalse
*
* @return void
*/
public function testReturnValueIsFalse($standard)
{
$config = new ConfigDouble(["--standard=$standard"]);
$ruleset = new Ruleset($config);
$testCaseFile = __DIR__.'/Fixtures/test.inc';
$phpcsFile = new LocalFile($testCaseFile, $ruleset, $config);
$phpcsFile->process();
$fixed = $phpcsFile->fixer->fixFile();
$this->assertFalse($fixed);
}//end testReturnValueIsFalse()
/**
* Data provider.
*
* @return array<string, array<string, string>>
*/
public static function dataReturnValueIsFalse()
{
return [
'when there is a fixer conflict' => [
'standard' => __DIR__.'/FixFileReturnValueConflictTest.xml',
],
'when the fixer ran out of loops before all fixes could be applied' => [
'standard' => __DIR__.'/FixFileReturnValueNotEnoughLoopsTest.xml',
],
];
}//end dataReturnValueIsFalse()
}//end class

View File

@@ -0,0 +1,10 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-BlankLinesAtEnd.inc
+++ PHP_CodeSniffer
@@ -5,7 +5,3 @@
if ($var) {
echo 'This line is tab indented';
}
-
-
-
-

View File

@@ -0,0 +1,11 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,9 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-BlankLinesAtStart.inc
+++ PHP_CodeSniffer
@@ -1,6 +1,3 @@
-
-
-
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;

View File

@@ -0,0 +1,10 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,8 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-LineAdded.inc
+++ PHP_CodeSniffer
@@ -1,4 +1,5 @@
<?php
+// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {

View File

@@ -0,0 +1,6 @@
<?php
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,8 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-LineRemoved.inc
+++ PHP_CodeSniffer
@@ -4,5 +4,4 @@
if ($var) {
echo 'This line is tab indented';
- echo 'And this line is not';
}

View File

@@ -0,0 +1,8 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
echo 'And this line is not';
}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,9 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-NoTrailingWhitespace.inc
+++ PHP_CodeSniffer
@@ -1,5 +1,5 @@
<?php
-// Comment with 2 spaces trailing whitespace.
+// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,9 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-TabsToSpaces.inc
+++ PHP_CodeSniffer
@@ -3,5 +3,5 @@
$var = FALSE;
if ($var) {
- echo 'This line is tab indented';
+ echo 'This line is tab indented';
}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,12 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-VarNameChanged.inc
+++ PHP_CodeSniffer
@@ -1,7 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
-$rav = FALSE;
+$var = FALSE;
-if ($rav) {
+if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$rav = FALSE;
if ($rav) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,8 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-WhiteSpaceAtEnd.inc
+++ PHP_CodeSniffer
@@ -4,4 +4,4 @@
if ($var) {
echo 'This line is tab indented';
-}
+}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,8 @@
--- tests/Core/Fixer/Fixtures/GenerateDiffTest-WhiteSpaceAtStart.inc
+++ PHP_CodeSniffer
@@ -1,4 +1,4 @@
- <?php
+<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,7 @@
<?php
// Comment with 2 spaces trailing whitespace.
$var = FALSE;
if ($var) {
echo 'This line is tab indented';
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Test fixture.
*
* @see \PHP_CodeSniffer\Tests\Core\Fixer\FixFileReturnValueTest
*/
namespace Fixtures\TestStandard\Sniffs\FixFileReturnValue;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
class AllGoodSniff implements Sniff
{
public function register()
{
return [T_ECHO];
}
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE
|| $tokens[($stackPtr + 1)]['length'] > 51
) {
return;
}
$error = 'There should be 52 spaces after an ECHO keyword';
$fix = $phpcsFile->addFixableError($error, ($stackPtr + 1), 'ShortSpace');
if ($fix === true) {
$phpcsFile->fixer->replaceToken(($stackPtr + 1), str_repeat(' ', 52));
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Test fixture.
*
* This sniff deliberately causes a fixer conflict **with no fixes applied in loop 50**.
* This last part is important as that's the exact situation which needs testing.
*
* Loop 1 applies the fix for `BlankLineAfterOpen` and then bows out.
* Loop 2 applies a fix for `NewlineEOF`.
* Loop 3 applies a fix for `NoNewlineEOF`.
* Loop 4 will try to apply the `NewlineEOF` fix again, but sees this causes a conflict and skips.
* Loop 5 will try to apply the `NewlineEOF` fix again, but sees this causes a conflict and skips.
* Loop 6 applies a fix for `NewlineEOF`.
* Loop 7 will try to apply the `NoNewlineEOF` fix again, but sees this causes a conflict and skips.
* Loop 8 applies a fix for `NoNewlineEOF`.
* Loop 9 - 13 repeat loop 4 - 8.
* Loop 14 - 18 repeat loop 4 - 8.
* Loop 19 - 23 repeat loop 4 - 8.
* Loop 24 - 28 repeat loop 4 - 8.
* Loop 29 - 33 repeat loop 4 - 8.
* Loop 34 - 38 repeat loop 4 - 8.
* Loop 39 - 43 repeat loop 4 - 8.
* Loop 44 - 48 repeat loop 4 - 8.
* Loop 49 = loop 4.
* Loop 50 = loop 5, i.e. applies no fixes.
*
* @see \PHP_CodeSniffer\Tests\Core\Fixer\FixFileReturnValueTest
*/
namespace Fixtures\TestStandard\Sniffs\FixFileReturnValue;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
class ConflictSniff implements Sniff
{
public function register()
{
return [T_OPEN_TAG];
}
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Demand a blank line after the PHP open tag.
// This error is here to ensure something will be fixed in the file.
$nextNonWhitespace = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
if (($tokens[$nextNonWhitespace]['line'] - $tokens[$stackPtr]['line']) !== 2) {
$error = 'There must be a single blank line after the PHP open tag';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'BlankLineAfterOpen');
if ($fix === true) {
$phpcsFile->fixer->addNewline($stackPtr);
// This return is here deliberately to force a new loop.
// This should ensure that loop 50 does *NOT* apply any fixes.
return;
}
}
// Skip to the end of the file.
$stackPtr = ($phpcsFile->numTokens - 1);
$eolCharLen = strlen($phpcsFile->eolChar);
$lastChars = substr($tokens[$stackPtr]['content'], ($eolCharLen * -1));
// Demand a newline at the end of a file.
if ($lastChars !== $phpcsFile->eolChar) {
$error = 'File must end with a newline character';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoNewlineEOF');
if ($fix === true) {
$phpcsFile->fixer->addNewline($stackPtr);
}
}
// Demand NO newline at the end of a file.
if ($lastChars === $phpcsFile->eolChar) {
$error = 'File must not end with a newline character';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'NewlineEOF');
if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
for ($i = $stackPtr; $i > 0; $i--) {
$newContent = rtrim($tokens[$i]['content'], $phpcsFile->eolChar);
$phpcsFile->fixer->replaceToken($i, $newContent);
if ($newContent !== '') {
break;
}
}
$phpcsFile->fixer->endChangeset();
}
}
// Ignore the rest of the file.
return $phpcsFile->numTokens;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Test fixture.
*
* This sniff deliberately causes a "failed to fix" situation by causing the fixer to run out of loops.
*
* @see \PHP_CodeSniffer\Tests\Core\Fixer\FixFileReturnValueTest
*/
namespace Fixtures\TestStandard\Sniffs\FixFileReturnValue;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
class NotEnoughLoopsSniff implements Sniff
{
public function register()
{
return [T_ECHO];
}
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE
|| $tokens[($stackPtr + 1)]['length'] > 60
) {
return;
}
$error = 'There should be 60 spaces after an ECHO keyword';
$fix = $phpcsFile->addFixableError($error, ($stackPtr + 1), 'ShortSpace');
if ($fix === true) {
// The fixer deliberately only adds one space in each loop to ensure it runs out of loops before the file complies.
$phpcsFile->fixer->addContent($stackPtr, ' ');
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="TestStandard" namespace="Fixtures\TestStandard" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
</ruleset>

View File

@@ -0,0 +1,2 @@
<?php
echo 'this is a simple file which will have a fixer conflict when the TestStandard.FixFileReturnValue.Conflict sniff is run against it';

View File

@@ -0,0 +1,227 @@
<?php
/**
* Tests for diff generation.
*
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
* @copyright 2024 Juliette Reinders Folmer. All rights reserved.
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/
namespace PHP_CodeSniffer\Tests\Core\Fixer;
use PHP_CodeSniffer\Files\LocalFile;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;
/**
* Tests for diff generation.
*
* Note: these tests are specifically about the Fixer::generateDiff() method and do not
* test running the fixer itself, nor generating a diff based on a fixer run.
*
* @covers PHP_CodeSniffer\Fixer::generateDiff
* @group Windows
*/
final class GenerateDiffTest extends TestCase
{
/**
* A \PHP_CodeSniffer\Files\File object to compare the files against.
*
* @var \PHP_CodeSniffer\Files\LocalFile
*/
private static $phpcsFile;
/**
* Initialize an \PHP_CodeSniffer\Files\File object with code.
*
* Things to take note of in the code snippet used for these tests:
* - Line endings are \n.
* - Tab indent.
* - Trailing whitespace.
*
* Also note that the Config object is deliberately created without a `tabWidth` setting to
* prevent doing tab replacement when parsing the file. This is to allow for testing a
* diff with tabs vs spaces (which wouldn't yield a diff if tabs had already been replaced).
*
* @beforeClass
*
* @return void
*/
public static function initializeFile()
{
$config = new ConfigDouble();
$ruleset = new Ruleset($config);
self::$phpcsFile = new LocalFile(__DIR__.'/Fixtures/GenerateDiffTest.inc', $ruleset, $config);
self::$phpcsFile->parse();
self::$phpcsFile->fixer->startFile(self::$phpcsFile);
}//end initializeFile()
/**
* Test generating a diff on the file object itself.
*
* @return void
*/
public function testGenerateDiffNoFile()
{
$diff = self::$phpcsFile->fixer->generateDiff(null, false);
$this->assertSame('', $diff);
}//end testGenerateDiffNoFile()
/**
* Test generating a diff between a PHPCS File object and a file on disk.
*
* @param string $filePath The path to the file to compare the File object against.
*
* @dataProvider dataGenerateDiff
*
* @return void
*/
public function testGenerateDiff($filePath)
{
$diff = self::$phpcsFile->fixer->generateDiff($filePath, false);
// Allow for the tests to pass on Windows too.
$diff = str_replace('--- tests\Core\Fixer/', '--- tests/Core/Fixer/', $diff);
$expectedDiffFile = str_replace('.inc', '.diff', $filePath);
$this->assertStringEqualsFile($expectedDiffFile, $diff);
}//end testGenerateDiff()
/**
* Data provider.
*
* @see testGenerateDiff()
*
* @return array<string, array<string, string>>
*/
public static function dataGenerateDiff()
{
return [
'no difference' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-NoDiff.inc',
],
'line removed' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-LineRemoved.inc',
],
'line added' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-LineAdded.inc',
],
'var name changed' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-VarNameChanged.inc',
],
'trailing whitespace removed' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-NoTrailingWhitespace.inc',
],
'tab replaced with spaces' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-TabsToSpaces.inc',
],
'blank lines at start of file' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-BlankLinesAtStart.inc',
],
'whitespace diff at start of file' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-WhiteSpaceAtStart.inc',
],
'blank lines at end of file' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-BlankLinesAtEnd.inc',
],
'whitespace diff at end of file' => [
'filePath' => __DIR__.'/Fixtures/GenerateDiffTest-WhiteSpaceAtEnd.inc',
],
];
}//end dataGenerateDiff()
/**
* Test generating a diff between a PHPCS File object and a file on disk and colourizing the output.
*
* @return void
*/
public function testGenerateDiffColoured()
{
$expected = "\033[31m--- tests/Core/Fixer/Fixtures/GenerateDiffTest-VarNameChanged.inc\033[0m".PHP_EOL;
$expected .= "\033[32m+++ PHP_CodeSniffer\033[0m".PHP_EOL;
$expected .= '@@ -1,7 +1,7 @@'.PHP_EOL;
$expected .= ' <?php'.PHP_EOL;
$expected .= ' // Comment with 2 spaces trailing whitespace. '.PHP_EOL;
$expected .= "\033[31m".'-$rav = FALSE;'."\033[0m".PHP_EOL;
$expected .= "\033[32m".'+$var = FALSE;'."\033[0m".PHP_EOL;
$expected .= ' '.PHP_EOL;
$expected .= "\033[31m".'-if ($rav) {'."\033[0m".PHP_EOL;
$expected .= "\033[32m".'+if ($var) {'."\033[0m".PHP_EOL;
$expected .= ' echo \'This line is tab indented\';'.PHP_EOL;
$expected .= ' }';
$filePath = __DIR__.'/Fixtures/GenerateDiffTest-VarNameChanged.inc';
$diff = self::$phpcsFile->fixer->generateDiff($filePath);
// Allow for the tests to pass on Windows too.
$diff = str_replace('--- tests\Core\Fixer/', '--- tests/Core/Fixer/', $diff);
$this->assertSame($expected, $diff);
}//end testGenerateDiffColoured()
/**
* Test generating a diff between a PHPCS File object using *nix line endings and a file on disk
* using Windows line endings.
*
* The point of this test is to verify that all lines are marked as having a difference.
* The actual lines endings used in the diff shown to the end-user are not relevant for this
* test.
* As the "diff" command is finicky with what type of line endings are used when the only
* difference on a line is the line ending, the test normalizes the line endings of the
* received diff before testing it.
*
* @return void
*/
public function testGenerateDiffDifferentLineEndings()
{
// By the looks of it, if the only diff between two files is line endings, the
// diff generated by the *nix "diff" command will always contain *nix line endings.
$expected = '--- tests/Core/Fixer/Fixtures/GenerateDiffTest-WindowsLineEndings.inc'."\n";
$expected .= '+++ PHP_CodeSniffer'."\n";
$expected .= '@@ -1,7 +1,7 @@'."\n";
$expected .= '-<?php'."\n";
$expected .= '-// Comment with 2 spaces trailing whitespace. '."\n";
$expected .= '-$var = FALSE;'."\n";
$expected .= '-'."\n";
$expected .= '-if ($var) {'."\n";
$expected .= '- echo \'This line is tab indented\';'."\n";
$expected .= '-}'."\n";
$expected .= '+<?php'."\n";
$expected .= '+// Comment with 2 spaces trailing whitespace. '."\n";
$expected .= '+$var = FALSE;'."\n";
$expected .= '+'."\n";
$expected .= '+if ($var) {'."\n";
$expected .= '+ echo \'This line is tab indented\';'."\n";
$expected .= '+}'."\n";
$filePath = __DIR__.'/Fixtures/GenerateDiffTest-WindowsLineEndings.inc';
$diff = self::$phpcsFile->fixer->generateDiff($filePath, false);
// Allow for the tests to pass on Windows too.
$diff = str_replace('--- tests\Core\Fixer/', '--- tests/Core/Fixer/', $diff);
// Normalize line endings of the diff.
$diff = preg_replace('`\R`', "\n", $diff);
$this->assertSame($expected, $diff);
}//end testGenerateDiffDifferentLineEndings()
}//end class

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="GeneratorTest" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
<config name="installed_paths" value="./tests/Core/Generators/Fixtures/"/>
<rule ref="StandardWithDocs">
<exclude name="StandardWithDocs.Structure.NoDocumentationElement"/>
</rule>
</ruleset>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="GeneratorTest" xsi:noNamespaceSchemaLocation="https://schema.phpcodesniffer.com/phpcs.xsd">
<config name="installed_paths" value="./tests/Core/Generators/Fixtures/"/>
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug1"/>
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug2"/>
<rule ref="StandardWithDocs.Content.DocumentationTitleToAnchorSlug3"/>
</ruleset>

View File

@@ -0,0 +1,105 @@
<html>
<head>
<title>GeneratorTest Coding Standards</title>
<style>
body {
background-color: #FFFFFF;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
color: #666666;
font-size: 20px;
font-weight: bold;
margin-top: 0px;
background-color: #E6E7E8;
padding: 20px;
border: 1px solid #BBBBBB;
}
h2 {
color: #00A5E3;
font-size: 16px;
font-weight: normal;
margin-top: 50px;
}
h2 a.sniffanchor,
h2 a.sniffanchor {
color: #006C95;
opacity: 0;
padding: 0 3px;
text-decoration: none;
font-weight: bold;
}
h2:hover a.sniffanchor,
h2:focus a.sniffanchor {
opacity: 1;
}
.code-comparison {
width: 100%;
}
.code-comparison td {
border: 1px solid #CCCCCC;
}
.code-comparison-title, .code-comparison-code {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
vertical-align: top;
padding: 4px;
width: 50%;
background-color: #F1F1F1;
line-height: 15px;
}
.code-comparison-title {
text-align: left;
font-weight: 600;
}
.code-comparison-code {
font-family: Courier;
background-color: #F9F9F9;
}
.code-comparison-highlight {
background-color: #DDF1F7;
border: 1px solid #00A5E3;
line-height: 15px;
}
.tag-line {
text-align: center;
width: 100%;
margin-top: 30px;
font-size: 12px;
}
.tag-line a {
color: #000000;
}
</style>
</head>
<body>
<h1>GeneratorTest Coding Standards</h1>
<h2 id="code-comparison--blank-lines">Code Comparison, blank lines<a class="sniffanchor" href="#code-comparison--blank-lines"> &sect; </a></h2>
<p class="text">This is a standard block.</p>
<table class="code-comparison">
<tr>
<th class="code-comparison-title">Valid: Checking handling of blank lines.</th>
<th class="code-comparison-title">Invalid: Checking handling of blank lines.</th>
</tr>
<tr>
<td class="code-comparison-code">//&nbsp;First&nbsp;line&nbsp;of&nbsp;the&nbsp;code&nbsp;sample&nbsp;is</br>//&nbsp;deliberately&nbsp;empty.</br></br>//&nbsp;We&nbsp;also&nbsp;have&nbsp;a&nbsp;blank&nbsp;line&nbsp;in&nbsp;the&nbsp;middle.</br></br>//&nbsp;And&nbsp;a&nbsp;blank&nbsp;line&nbsp;at&nbsp;the&nbsp;end.</td>
<td class="code-comparison-code">//&nbsp;First&nbsp;line&nbsp;of&nbsp;the&nbsp;code&nbsp;sample&nbsp;is</br>//&nbsp;deliberately&nbsp;empty.</br></br>//&nbsp;We&nbsp;also&nbsp;have&nbsp;a&nbsp;blank&nbsp;line&nbsp;in&nbsp;the&nbsp;middle.</br></br>//&nbsp;And&nbsp;a&nbsp;blank&nbsp;line&nbsp;at&nbsp;the&nbsp;end.</td>
</tr>
</table>
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
</body>
</html>

View File

@@ -0,0 +1,35 @@
# GeneratorTest Coding Standard
## Code Comparison, blank lines
This is a standard block.
<table>
<tr>
<th>Valid: Checking handling of blank lines.</th>
<th>Invalid: Checking handling of blank lines.</th>
</tr>
<tr>
<td>
// First line of the code sample is
// deliberately empty.
// We also have a blank line in the middle.
// And a blank line at the end.
</td>
<td>
// First line of the code sample is
// deliberately empty.
// We also have a blank line in the middle.
// And a blank line at the end.
</td>
</tr>
</table>
Documentation generated on *REDACTED* by [PHP_CodeSniffer *VERSION*](https://github.com/PHPCSStandards/PHP_CodeSniffer)

View File

@@ -0,0 +1,18 @@
---------------------------------------------------------------
| GENERATORTEST CODING STANDARD: CODE COMPARISON, BLANK LINES |
---------------------------------------------------------------
This is a standard block.
----------------------------------------- CODE COMPARISON ------------------------------------------
| Valid: Checking handling of blank lines. | Invalid: Checking handling of blank lines. |
----------------------------------------------------------------------------------------------------
| // First line of the code sample is | // First line of the code sample is |
| // deliberately empty. | // deliberately empty. |
| | |
| // We also have a blank line in the middle. | // We also have a blank line in the middle. |
| | |
| // And a blank line at the end. | // And a blank line at the end. |
----------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,115 @@
<html>
<head>
<title>GeneratorTest Coding Standards</title>
<style>
body {
background-color: #FFFFFF;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
color: #666666;
font-size: 20px;
font-weight: bold;
margin-top: 0px;
background-color: #E6E7E8;
padding: 20px;
border: 1px solid #BBBBBB;
}
h2 {
color: #00A5E3;
font-size: 16px;
font-weight: normal;
margin-top: 50px;
}
h2 a.sniffanchor,
h2 a.sniffanchor {
color: #006C95;
opacity: 0;
padding: 0 3px;
text-decoration: none;
font-weight: bold;
}
h2:hover a.sniffanchor,
h2:focus a.sniffanchor {
opacity: 1;
}
.code-comparison {
width: 100%;
}
.code-comparison td {
border: 1px solid #CCCCCC;
}
.code-comparison-title, .code-comparison-code {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
vertical-align: top;
padding: 4px;
width: 50%;
background-color: #F1F1F1;
line-height: 15px;
}
.code-comparison-title {
text-align: left;
font-weight: 600;
}
.code-comparison-code {
font-family: Courier;
background-color: #F9F9F9;
}
.code-comparison-highlight {
background-color: #DDF1F7;
border: 1px solid #00A5E3;
line-height: 15px;
}
.tag-line {
text-align: center;
width: 100%;
margin-top: 30px;
font-size: 12px;
}
.tag-line a {
color: #000000;
}
</style>
</head>
<body>
<h1>GeneratorTest Coding Standards</h1>
<h2 id="code-comparison--block-length">Code Comparison, block length<a class="sniffanchor" href="#code-comparison--block-length"> &sect; </a></h2>
<p class="text">This is a standard block.</p>
<table class="code-comparison">
<tr>
<th class="code-comparison-title">Valid: code sample A has more lines than B.</th>
<th class="code-comparison-title">Invalid: shorter.</th>
</tr>
<tr>
<td class="code-comparison-code">//&nbsp;This&nbsp;code&nbsp;sample&nbsp;has&nbsp;more&nbsp;lines</br>//&nbsp;than&nbsp;the&nbsp;"invalid"&nbsp;one.</br><span class="code-comparison-highlight">$one</span>&nbsp;=&nbsp;10;</td>
<td class="code-comparison-code"><span class="code-comparison-highlight">$a</span>&nbsp;=&nbsp;10;</td>
</tr>
</table>
<table class="code-comparison">
<tr>
<th class="code-comparison-title">Valid: shorter.</th>
<th class="code-comparison-title">Invalid: code sample B has more lines than A.</th>
</tr>
<tr>
<td class="code-comparison-code"><span class="code-comparison-highlight">echo</span>&nbsp;$foo;</td>
<td class="code-comparison-code">//&nbsp;This&nbsp;code&nbsp;sample&nbsp;has&nbsp;more&nbsp;lines</br>//&nbsp;than&nbsp;the&nbsp;"valid"&nbsp;one.</br><span class="code-comparison-highlight">print</span>&nbsp;$foo;</td>
</tr>
</table>
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
</body>
</html>

View File

@@ -0,0 +1,47 @@
# GeneratorTest Coding Standard
## Code Comparison, block length
This is a standard block.
<table>
<tr>
<th>Valid: code sample A has more lines than B.</th>
<th>Invalid: shorter.</th>
</tr>
<tr>
<td>
// This code sample has more lines
// than the "invalid" one.
$one = 10;
</td>
<td>
$a = 10;
</td>
</tr>
</table>
<table>
<tr>
<th>Valid: shorter.</th>
<th>Invalid: code sample B has more lines than A.</th>
</tr>
<tr>
<td>
echo $foo;
</td>
<td>
// This code sample has more lines
// than the "valid" one.
print $foo;
</td>
</tr>
</table>
Documentation generated on *REDACTED* by [PHP_CodeSniffer *VERSION*](https://github.com/PHPCSStandards/PHP_CodeSniffer)

View File

@@ -0,0 +1,23 @@
----------------------------------------------------------------
| GENERATORTEST CODING STANDARD: CODE COMPARISON, BLOCK LENGTH |
----------------------------------------------------------------
This is a standard block.
----------------------------------------- CODE COMPARISON ------------------------------------------
| Valid: code sample A has more lines than B. | Invalid: shorter. |
----------------------------------------------------------------------------------------------------
| // This code sample has more lines | $a = 10; |
| // than the "invalid" one. | |
| $one = 10; | |
----------------------------------------------------------------------------------------------------
----------------------------------------- CODE COMPARISON ------------------------------------------
| Valid: shorter. | Invalid: code sample B has more lines than A. |
----------------------------------------------------------------------------------------------------
| echo $foo; | // This code sample has more lines |
| | // than the "valid" one. |
| | print $foo; |
----------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,105 @@
<html>
<head>
<title>GeneratorTest Coding Standards</title>
<style>
body {
background-color: #FFFFFF;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
color: #666666;
font-size: 20px;
font-weight: bold;
margin-top: 0px;
background-color: #E6E7E8;
padding: 20px;
border: 1px solid #BBBBBB;
}
h2 {
color: #00A5E3;
font-size: 16px;
font-weight: normal;
margin-top: 50px;
}
h2 a.sniffanchor,
h2 a.sniffanchor {
color: #006C95;
opacity: 0;
padding: 0 3px;
text-decoration: none;
font-weight: bold;
}
h2:hover a.sniffanchor,
h2:focus a.sniffanchor {
opacity: 1;
}
.code-comparison {
width: 100%;
}
.code-comparison td {
border: 1px solid #CCCCCC;
}
.code-comparison-title, .code-comparison-code {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
vertical-align: top;
padding: 4px;
width: 50%;
background-color: #F1F1F1;
line-height: 15px;
}
.code-comparison-title {
text-align: left;
font-weight: 600;
}
.code-comparison-code {
font-family: Courier;
background-color: #F9F9F9;
}
.code-comparison-highlight {
background-color: #DDF1F7;
border: 1px solid #00A5E3;
line-height: 15px;
}
.tag-line {
text-align: center;
width: 100%;
margin-top: 30px;
font-size: 12px;
}
.tag-line a {
color: #000000;
}
</style>
</head>
<body>
<h1>GeneratorTest Coding Standards</h1>
<h2 id="code-comparison--char-encoding">Code Comparison, char encoding<a class="sniffanchor" href="#code-comparison--char-encoding"> &sect; </a></h2>
<p class="text">This is a standard block.</p>
<table class="code-comparison">
<tr>
<th class="code-comparison-title">Valid: Vestibulum et orci condimentum.</th>
<th class="code-comparison-title">Invalid: Donec in nisl ut tortor convallis interdum.</th>
</tr>
<tr>
<td class="code-comparison-code">&lt;?php</br></br>//&nbsp;The&nbsp;above&nbsp;PHP&nbsp;tag&nbsp;is&nbsp;specifically&nbsp;testing</br>//&nbsp;handling&nbsp;of&nbsp;that&nbsp;in&nbsp;generated&nbsp;HTML&nbsp;doc.</br></br>//&nbsp;Now&nbsp;let's&nbsp;also&nbsp;check&nbsp;the&nbsp;handling&nbsp;of</br>//&nbsp;comparison&nbsp;operators&nbsp;in&nbsp;code&nbsp;samples...</br>$a&nbsp;=&nbsp;$b&nbsp;<&nbsp;$c;</br>$d&nbsp;=&nbsp;$e&nbsp;>&nbsp;$f;</br>$g&nbsp;=&nbsp;$h&nbsp;<=&nbsp;$i;</br>$j&nbsp;=&nbsp;$k&nbsp;>=&nbsp;$l;</br>$m&nbsp;=&nbsp;$n&nbsp;<=>&nbsp;$o;</td>
<td class="code-comparison-code"><span class="code-comparison-highlight">&lt;?php</span></br></br>//&nbsp;The&nbsp;above&nbsp;PHP&nbsp;tag&nbsp;is&nbsp;specifically&nbsp;testing</br>//&nbsp;handling&nbsp;of&nbsp;that&nbsp;in&nbsp;generated&nbsp;HTML&nbsp;doc.</br></br>//&nbsp;Now&nbsp;let's&nbsp;also&nbsp;check&nbsp;the&nbsp;handling&nbsp;of</br>//&nbsp;comparison&nbsp;operators&nbsp;in&nbsp;code&nbsp;samples</br>//&nbsp;in&nbsp;combination&nbsp;with&nbsp;"em"&nbsp;tags.</br>$a&nbsp;=&nbsp;$b&nbsp;<span class="code-comparison-highlight"><</span>&nbsp;$c;</br>$d&nbsp;=&nbsp;$e&nbsp;>&nbsp;$f;</br>$g&nbsp;=&nbsp;<span class="code-comparison-highlight">$h&nbsp;<=&nbsp;$i</span>;</br>$j&nbsp;=&nbsp;$k&nbsp;>=&nbsp;$l;</br>$m&nbsp;=&nbsp;$n&nbsp;<span class="code-comparison-highlight"><=></span>&nbsp;$o;</td>
</tr>
</table>
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
</body>
</html>

View File

@@ -0,0 +1,48 @@
# GeneratorTest Coding Standard
## Code Comparison, char encoding
This is a standard block.
<table>
<tr>
<th>Valid: Vestibulum et orci condimentum.</th>
<th>Invalid: Donec in nisl ut tortor convallis interdum.</th>
</tr>
<tr>
<td>
<?php
// The above PHP tag is specifically testing
// handling of that in generated HTML doc.
// Now let's also check the handling of
// comparison operators in code samples...
$a = $b < $c;
$d = $e > $f;
$g = $h <= $i;
$j = $k >= $l;
$m = $n <=> $o;
</td>
<td>
<?php
// The above PHP tag is specifically testing
// handling of that in generated HTML doc.
// Now let's also check the handling of
// comparison operators in code samples
// in combination with "em" tags.
$a = $b < $c;
$d = $e > $f;
$g = $h <= $i;
$j = $k >= $l;
$m = $n <=> $o;
</td>
</tr>
</table>
Documentation generated on *REDACTED* by [PHP_CodeSniffer *VERSION*](https://github.com/PHPCSStandards/PHP_CodeSniffer)

View File

@@ -0,0 +1,26 @@
-----------------------------------------------------------------
| GENERATORTEST CODING STANDARD: CODE COMPARISON, CHAR ENCODING |
-----------------------------------------------------------------
This is a standard block.
----------------------------------------- CODE COMPARISON ------------------------------------------
| Valid: Vestibulum et orci condimentum. | Invalid: Donec in nisl ut tortor convallis |
| | interdum. |
----------------------------------------------------------------------------------------------------
| <?php | <?php |
| | |
| // The above PHP tag is specifically testing | // The above PHP tag is specifically testing |
| // handling of that in generated HTML doc. | // handling of that in generated HTML doc. |
| | |
| // Now let's also check the handling of | // Now let's also check the handling of |
| // comparison operators in code samples... | // comparison operators in code samples |
| $a = $b < $c; | // in combination with "em" tags. |
| $d = $e > $f; | $a = $b < $c; |
| $g = $h <= $i; | $d = $e > $f; |
| $j = $k >= $l; | $g = $h <= $i; |
| $m = $n <=> $o; | $j = $k >= $l; |
| | $m = $n <=> $o; |
----------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,106 @@
<html>
<head>
<title>GeneratorTest Coding Standards</title>
<style>
body {
background-color: #FFFFFF;
font-size: 14px;
font-family: Arial, Helvetica, sans-serif;
color: #000000;
}
h1 {
color: #666666;
font-size: 20px;
font-weight: bold;
margin-top: 0px;
background-color: #E6E7E8;
padding: 20px;
border: 1px solid #BBBBBB;
}
h2 {
color: #00A5E3;
font-size: 16px;
font-weight: normal;
margin-top: 50px;
}
h2 a.sniffanchor,
h2 a.sniffanchor {
color: #006C95;
opacity: 0;
padding: 0 3px;
text-decoration: none;
font-weight: bold;
}
h2:hover a.sniffanchor,
h2:focus a.sniffanchor {
opacity: 1;
}
.code-comparison {
width: 100%;
}
.code-comparison td {
border: 1px solid #CCCCCC;
}
.code-comparison-title, .code-comparison-code {
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
vertical-align: top;
padding: 4px;
width: 50%;
background-color: #F1F1F1;
line-height: 15px;
}
.code-comparison-title {
text-align: left;
font-weight: 600;
}
.code-comparison-code {
font-family: Courier;
background-color: #F9F9F9;
}
.code-comparison-highlight {
background-color: #DDF1F7;
border: 1px solid #00A5E3;
line-height: 15px;
}
.tag-line {
text-align: center;
width: 100%;
margin-top: 30px;
font-size: 12px;
}
.tag-line a {
color: #000000;
}
</style>
</head>
<body>
<h1>GeneratorTest Coding Standards</h1>
<h2 id="code-comparison--line-length">Code Comparison, line length<a class="sniffanchor" href="#code-comparison--line-length"> &sect; </a></h2>
<p class="text">Ensure there is no PHP &quot;Warning: str_repeat(): Second argument has to be greater than or equal to 0&quot;.<br/>
Ref: squizlabs/PHP_CodeSniffer#2522</p>
<table class="code-comparison">
<tr>
<th class="code-comparison-title">Valid: contains line which is too long.</th>
<th class="code-comparison-title">Invalid: contains line which is too long.</th>
</tr>
<tr>
<td class="code-comparison-code">class&nbsp;Foo&nbsp;extends&nbsp;Bar&nbsp;implements&nbsp;<span class="code-comparison-highlight">Countable</span>,&nbsp;Serializable</br>{</br>}</td>
<td class="code-comparison-code">class&nbsp;Foo&nbsp;extends&nbsp;Bar</br>{</br>&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;function&nbsp;<span class="code-comparison-highlight">foobar</span>($param1,&nbsp;$param2)&nbsp;{}</br>}</td>
</tr>
</table>
<div class="tag-line">Documentation generated on #REDACTED# by <a href="https://github.com/PHPCSStandards/PHP_CodeSniffer">PHP_CodeSniffer #VERSION#</a></div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
# GeneratorTest Coding Standard
## Code Comparison, line length
Ensure there is no PHP &quot;Warning: str_repeat(): Second argument has to be greater than or equal to 0&quot;.
Ref: squizlabs/PHP_CodeSniffer#2522
<table>
<tr>
<th>Valid: contains line which is too long.</th>
<th>Invalid: contains line which is too long.</th>
</tr>
<tr>
<td>
class Foo extends Bar implements Countable, Serializable
{
}
</td>
<td>
class Foo extends Bar
{
public static function foobar($param1, $param2) {}
}
</td>
</tr>
</table>
Documentation generated on *REDACTED* by [PHP_CodeSniffer *VERSION*](https://github.com/PHPCSStandards/PHP_CodeSniffer)

Some files were not shown because too many files have changed in this diff Show More