diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 68238d3023..aa706fbfa9 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -620,7 +620,7 @@ public function replaceTabsInToken(&$token, $prefix=' ', $padding=' ', $tabWidth if ($content !== '') { $newContent .= $content; if ($checkEncoding === true) { - // Not using the default encoding, so take a bit more care. + // Not using ASCII encoding, so take a bit more care. $oldLevel = error_reporting(); error_reporting(0); $contentLength = iconv_strlen($content, $this->config->encoding); diff --git a/tests/Core/Tokenizers/Tokenizer/CreatePositionMapTabWidth0Test.php b/tests/Core/Tokenizers/Tokenizer/CreatePositionMapTabWidth0Test.php new file mode 100644 index 0000000000..fd47bf7da5 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/CreatePositionMapTabWidth0Test.php @@ -0,0 +1,107 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +/** + * Tab replacement test using tab width 0, means no tab replacement will take place. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::createPositionMap + */ +final class CreatePositionMapTabWidth0Test extends ReplaceTabsInTokenTestCase +{ + + /** + * The tab width setting to use when tokenizing the file. + * + * @var integer + */ + protected $tabWidth = 0; + + + /** + * Data provider helper. + * + * @see ReplaceTabsInTokenTestCase::dataTabReplacement() + * + * @return array> + */ + public static function getTabReplacementExpected() + { + return [ + 'Tab indentation' => [ + 'length' => 2, + 'content' => ' ', + 'orig_content' => null, + ], + 'Mixed tab/space indentation' => [ + 'length' => 3, + 'content' => ' ', + 'orig_content' => null, + ], + 'Inline: single tab in text string' => [ + 'length' => 15, + 'content' => "'tab separated'", + 'orig_content' => null, + ], + 'Inline: single tab between each word in text string' => [ + 'length' => 24, + 'content' => '"tab $between each word"', + 'orig_content' => null, + ], + 'Inline: multiple tabs in heredoc' => [ + 'length' => 15, + 'content' => 'tab separated +', + 'orig_content' => null, + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'length' => 27, + 'content' => 'tab between each word +', + 'orig_content' => null, + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'length' => 20, + 'content' => "'tab separated'", + 'orig_content' => null, + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'length' => 31, + 'content' => '"tab $between each word"', + 'orig_content' => null, + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'length' => 50, + 'content' => '// -123 With tabwidth 4, the tab size should be 1. +', + 'orig_content' => null, + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'length' => 52, + 'content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + 'orig_content' => null, + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'length' => 45, + 'content' => '-1 With tabwidth 4, the tab size should be 3.', + 'orig_content' => null, + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'length' => 47, + 'content' => '// - With tabwidth 4, the tab size should be 4. +', + 'orig_content' => null, + ], + ]; + + }//end getTabReplacementExpected() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenMiscTest.php b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenMiscTest.php new file mode 100644 index 0000000000..f4bd711a6f --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenMiscTest.php @@ -0,0 +1,124 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +use PHP_CodeSniffer\Files\DummyFile; +use PHP_CodeSniffer\Ruleset; +use PHP_CodeSniffer\Tests\ConfigDouble; +use PHPUnit\Framework\TestCase; + +/** + * Miscellaneous tests for tab replacement. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +final class ReplaceTabsInTokenMiscTest extends TestCase +{ + + + /** + * Test that when no tab width is set or passed, the tab width will be set to 1. + * + * @return void + */ + public function testTabWidthNotSet() + { + $config = new ConfigDouble(); + $ruleset = new Ruleset($config); + + $content = <<parse(); + + $tokens = $phpcsFile->getTokens(); + $target = $phpcsFile->findNext(T_WHITESPACE, 0); + + // Verify initial state. + $this->assertTrue(is_int($target), 'Target token was not found'); + $this->assertSame(' ', $tokens[$target]['content'], 'Content after initial parsing does not contain tabs'); + $this->assertSame(2, $tokens[$target]['length'], 'Length after initial parsing is not as expected'); + $this->assertArrayNotHasKey('orig_content', $tokens[$target], "Key 'orig_content' found in the initial token array."); + + $phpcsFile->tokenizer->replaceTabsInToken($tokens[$target]); + + // Verify tab replacement. + $this->assertSame(' ', $tokens[$target]['content'], 'Content after tab replacement is not as expected'); + $this->assertSame(2, $tokens[$target]['length'], 'Length after tab replacement is not as expected'); + $this->assertArrayHasKey('orig_content', $tokens[$target], "Key 'orig_content' not found in the token array."); + + }//end testTabWidthNotSet() + + + /** + * Test that the length calculation handles text in non-ascii encodings correctly. + * + * @requires extension iconv + * + * @return void + */ + public function testLengthSettingRespectsEncoding() + { + $config = new ConfigDouble(); + $config->tabWidth = 4; + $ruleset = new Ruleset($config); + + $content = <<parse(); + + $tokens = $phpcsFile->getTokens(); + $target = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, 0); + + $this->assertTrue(is_int($target), 'Target token was not found'); + $this->assertSame("'пасха пасха'", $tokens[$target]['content'], 'Content is not as expected'); + $this->assertSame(17, $tokens[$target]['length'], 'Length is not as expected'); + $this->assertArrayHasKey('orig_content', $tokens[$target], "Key 'orig_content' not found in the token array."); + $this->assertSame("'пасха пасха'", $tokens[$target]['orig_content'], 'Orig_content is not as expected'); + + }//end testLengthSettingRespectsEncoding() + + + /** + * Test that the length calculation falls back to byte length if iconv detects an illegal character. + * + * @requires extension iconv + * + * @return void + */ + public function testLengthSettingFallsBackToBytesWhenTextContainsIllegalChars() + { + $config = new ConfigDouble(); + $config->tabWidth = 4; + $ruleset = new Ruleset($config); + + $content = <<parse(); + + $tokens = $phpcsFile->getTokens(); + $target = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, 0); + + $this->assertTrue(is_int($target), 'Target token was not found'); + $this->assertSame(11, $tokens[$target]['length'], 'Length is not as expected'); + $this->assertArrayHasKey('orig_content', $tokens[$target], "Key 'orig_content' not found in the token array."); + + }//end testLengthSettingFallsBackToBytesWhenTextContainsIllegalChars() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth1Test.php b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth1Test.php new file mode 100644 index 0000000000..2962153454 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth1Test.php @@ -0,0 +1,111 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +/** + * Tab replacement test using tab width 1. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +final class ReplaceTabsInTokenTabWidth1Test extends ReplaceTabsInTokenTestCase +{ + + /** + * The tab width setting to use when tokenizing the file. + * + * @var integer + */ + protected $tabWidth = 1; + + + /** + * Data provider helper. + * + * @see ReplaceTabsInTokenTestCase::dataTabReplacement() + * + * @return array> + */ + public static function getTabReplacementExpected() + { + return [ + 'Tab indentation' => [ + 'length' => 2, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Mixed tab/space indentation' => [ + 'length' => 3, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Inline: single tab in text string' => [ + 'length' => 15, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: single tab between each word in text string' => [ + 'length' => 24, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: multiple tabs in heredoc' => [ + 'length' => 15, + 'content' => 'tab separated +', + 'orig_content' => 'tab separated +', + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'length' => 27, + 'content' => 'tab between each word +', + 'orig_content' => 'tab between each word +', + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'length' => 20, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'length' => 31, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'length' => 50, + 'content' => '// -123 With tabwidth 4, the tab size should be 1. +', + 'orig_content' => '// -123 With tabwidth 4, the tab size should be 1. +', + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'length' => 52, + 'content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + 'orig_content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'length' => 45, + 'content' => '-1 With tabwidth 4, the tab size should be 3.', + 'orig_content' => '-1 With tabwidth 4, the tab size should be 3.', + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'length' => 47, + 'content' => '// - With tabwidth 4, the tab size should be 4. +', + 'orig_content' => '// - With tabwidth 4, the tab size should be 4. +', + ], + ]; + + }//end getTabReplacementExpected() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth2Test.php b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth2Test.php new file mode 100644 index 0000000000..92de221d89 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth2Test.php @@ -0,0 +1,111 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +/** + * Tab replacement test using tab width 2. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +final class ReplaceTabsInTokenTabWidth2Test extends ReplaceTabsInTokenTestCase +{ + + /** + * The tab width setting to use when tokenizing the file. + * + * @var integer + */ + protected $tabWidth = 2; + + + /** + * Data provider helper. + * + * @see ReplaceTabsInTokenTestCase::dataTabReplacement() + * + * @return array> + */ + public static function getTabReplacementExpected() + { + return [ + 'Tab indentation' => [ + 'length' => 4, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Mixed tab/space indentation' => [ + 'length' => 4, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Inline: single tab in text string' => [ + 'length' => 15, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: single tab between each word in text string' => [ + 'length' => 26, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: multiple tabs in heredoc' => [ + 'length' => 17, + 'content' => 'tab separated +', + 'orig_content' => 'tab separated +', + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'length' => 34, + 'content' => 'tab between each word +', + 'orig_content' => 'tab between each word +', + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'length' => 23, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'length' => 32, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'length' => 50, + 'content' => '// -123 With tabwidth 4, the tab size should be 1. +', + 'orig_content' => '// -123 With tabwidth 4, the tab size should be 1. +', + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'length' => 53, + 'content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + 'orig_content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'length' => 45, + 'content' => '-1 With tabwidth 4, the tab size should be 3.', + 'orig_content' => '-1 With tabwidth 4, the tab size should be 3.', + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'length' => 48, + 'content' => '// - With tabwidth 4, the tab size should be 4. +', + 'orig_content' => '// - With tabwidth 4, the tab size should be 4. +', + ], + ]; + + }//end getTabReplacementExpected() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth4Test.php b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth4Test.php new file mode 100644 index 0000000000..5f74187989 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth4Test.php @@ -0,0 +1,111 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +/** + * Tab replacement test using tab width 4. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +final class ReplaceTabsInTokenTabWidth4Test extends ReplaceTabsInTokenTestCase +{ + + /** + * The tab width setting to use when tokenizing the file. + * + * @var integer + */ + protected $tabWidth = 4; + + + /** + * Data provider helper. + * + * @see ReplaceTabsInTokenTestCase::dataTabReplacement() + * + * @return array> + */ + public static function getTabReplacementExpected() + { + return [ + 'Tab indentation' => [ + 'length' => 8, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Mixed tab/space indentation' => [ + 'length' => 6, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Inline: single tab in text string' => [ + 'length' => 17, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: single tab between each word in text string' => [ + 'length' => 32, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: multiple tabs in heredoc' => [ + 'length' => 21, + 'content' => 'tab separated +', + 'orig_content' => 'tab separated +', + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'length' => 48, + 'content' => 'tab between each word +', + 'orig_content' => 'tab between each word +', + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'length' => 25, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'length' => 36, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'length' => 50, + 'content' => '// -123 With tabwidth 4, the tab size should be 1. +', + 'orig_content' => '// -123 With tabwidth 4, the tab size should be 1. +', + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'length' => 53, + 'content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + 'orig_content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'length' => 47, + 'content' => '-1 With tabwidth 4, the tab size should be 3.', + 'orig_content' => '-1 With tabwidth 4, the tab size should be 3.', + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'length' => 50, + 'content' => '// - With tabwidth 4, the tab size should be 4. +', + 'orig_content' => '// - With tabwidth 4, the tab size should be 4. +', + ], + ]; + + }//end getTabReplacementExpected() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth5Test.php b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth5Test.php new file mode 100644 index 0000000000..361894fb94 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTabWidth5Test.php @@ -0,0 +1,111 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +/** + * Tab replacement test using tab width 5. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +final class ReplaceTabsInTokenTabWidth5Test extends ReplaceTabsInTokenTestCase +{ + + /** + * The tab width setting to use when tokenizing the file. + * + * @var integer + */ + protected $tabWidth = 5; + + + /** + * Data provider helper. + * + * @see ReplaceTabsInTokenTestCase::dataTabReplacement() + * + * @return array> + */ + public static function getTabReplacementExpected() + { + return [ + 'Tab indentation' => [ + 'length' => 10, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Mixed tab/space indentation' => [ + 'length' => 7, + 'content' => ' ', + 'orig_content' => ' ', + ], + 'Inline: single tab in text string' => [ + 'length' => 15, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: single tab between each word in text string' => [ + 'length' => 25, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: multiple tabs in heredoc' => [ + 'length' => 24, + 'content' => 'tab separated +', + 'orig_content' => 'tab separated +', + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'length' => 54, + 'content' => 'tab between each word +', + 'orig_content' => 'tab between each word +', + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'length' => 30, + 'content' => "'tab separated'", + 'orig_content' => "'tab separated'", + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'length' => 35, + 'content' => '"tab $between each word"', + 'orig_content' => '"tab $between each word"', + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'length' => 52, + 'content' => '// -123 With tabwidth 4, the tab size should be 1. +', + 'orig_content' => '// -123 With tabwidth 4, the tab size should be 1. +', + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'length' => 55, + 'content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + 'orig_content' => '/* -12 With tabwidth 4, the tab size should be 2. */', + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'length' => 49, + 'content' => '-1 With tabwidth 4, the tab size should be 3.', + 'orig_content' => '-1 With tabwidth 4, the tab size should be 3.', + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'length' => 47, + 'content' => '// - With tabwidth 4, the tab size should be 4. +', + 'orig_content' => '// - With tabwidth 4, the tab size should be 4. +', + ], + ]; + + }//end getTabReplacementExpected() + + +}//end class diff --git a/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTest.inc b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTest.inc new file mode 100644 index 0000000000..e6046500e5 --- /dev/null +++ b/tests/Core/Tokenizers/Tokenizer/ReplaceTabsInTokenTest.inc @@ -0,0 +1,46 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Tokenizers\Tokenizer; + +use Exception; +use PHP_CodeSniffer\Tests\Core\Tokenizers\AbstractTokenizerTestCase; + +/** + * Tab replacement test case. + * + * @covers PHP_CodeSniffer\Tokenizers\Tokenizer::replaceTabsInToken + */ +abstract class ReplaceTabsInTokenTestCase extends AbstractTokenizerTestCase +{ + + /** + * The name of the test case file used by this test. + * + * @var string + */ + private static $caseFileName; + + + /** + * Make a copy the test case file we want to use for this test (as the file will be used by multiple tests). + * + * @beforeClass + * + * @return void + * + * @throws \Exception In case the base test case file would not be available. + */ + public static function copyCaseFile() + { + $relativeCN = str_replace(__NAMESPACE__.'\\', '', get_called_class()); + self::$caseFileName = __DIR__.DIRECTORY_SEPARATOR.$relativeCN.'.inc'; + + $baseFileName = realpath(__DIR__.'/ReplaceTabsInTokenTest.inc'); + if (is_string($baseFileName) === false) { + throw new Exception('Base test case file "ReplaceTabsInTokenTest.inc" not found'); + } + + if (copy($baseFileName, self::$caseFileName) === false) { + throw new Exception(sprintf('Failed to copy test case file "ReplaceTabsInTokenTest.inc" to %s', self::$caseFileName)); + } + + }//end copyCaseFile() + + + /** + * Delete the copied test case file after use. + * + * @afterClass + * + * @return void + */ + public static function deleteCaseFile() + { + @unlink(self::$caseFileName); + + }//end deleteCaseFile() + + + /** + * Verify that if a token not containing tabs would be passed to the replaceTabsInToken() method, + * yes, the `orig_content` key is added, but no changes are made to the token `content` or `length` values. + * + * @param string $testMarker The comment prefacing the target token. + * @param int|string $testTarget Token code to look for. + * @param array $expected Expectations for the token array. + * @param int $offset Optional. Offset from the target token to get to the _real_ target. + * This is specifically needed to target indentation whitespace. + * + * @dataProvider dataNoReplacementsAreMadeWhenNoTabsAreFound + * + * @return void + */ + public function testNoReplacementsAreMadeWhenNoTabsAreFound($testMarker, $testTarget, $expected, $offset=0) + { + $tokens = $this->phpcsFile->getTokens(); + $target = $this->getTargetToken($testMarker, $testTarget); + $target += $offset; + + foreach ($expected as $key => $value) { + if ($key === 'orig_content' && $value === null) { + $this->assertArrayNotHasKey($key, $tokens[$target], "Unexpected 'orig_content' key found in the token array."); + continue; + } + + $this->assertArrayHasKey($key, $tokens[$target], "Key $key not found in the token array."); + $this->assertSame($value, $tokens[$target][$key], "Value for key $key does not match expectation."); + } + + }//end testNoReplacementsAreMadeWhenNoTabsAreFound() + + + /** + * Data provider. + * + * @see testNoReplacementsAreMadeWhenNoTabsAreFound() + * + * @return array>> + */ + public static function dataNoReplacementsAreMadeWhenNoTabsAreFound() + { + return [ + 'Indentation whitespace, only spaces' => [ + 'testMarker' => '/* testNoReplacementNeeded */', + 'testTarget' => T_WHITESPACE, + 'expected' => [ + 'length' => 4, + 'content' => ' ', + 'orig_content' => null, + ], + 'offset' => 1, + ], + 'Trailing comment not containing any tabs' => [ + 'testMarker' => '/* testNoReplacementNeeded */', + 'testTarget' => T_COMMENT, + 'expected' => [ + 'length' => 35, + 'content' => '// Comment not containing any tabs. +', + 'orig_content' => null, + ], + ], + ]; + + }//end dataNoReplacementsAreMadeWhenNoTabsAreFound() + + + /** + * Test tab replacement in tokens. + * + * @param string $testMarker The comment prefacing the target token. + * @param int|string $testTarget Token code to look for. + * @param array $expected Expectations for the token array. + * @param int $offset Optional. Offset from the target token to get to the _real_ target. + * This is specifically needed to target indentation whitespace. + * + * @dataProvider dataTabReplacement + * + * @return void + */ + public function testTabReplacement($testMarker, $testTarget, $expected, $offset=0) + { + $tokens = $this->phpcsFile->getTokens(); + $target = $this->getTargetToken($testMarker, $testTarget); + $target += $offset; + + foreach ($expected as $key => $value) { + if ($key === 'orig_content' && $value === null) { + $this->assertArrayNotHasKey($key, $tokens[$target], "Unexpected 'orig_content' key found in the token array."); + continue; + } + + $this->assertArrayHasKey($key, $tokens[$target], "Key $key not found in the token array."); + $this->assertSame($value, $tokens[$target][$key], "Value for key $key does not match expectation."); + } + + }//end testTabReplacement() + + + /** + * Data provider. + * + * @see testTabReplacement() + * + * @return array>> + * + * @throws \Exception When the getTabReplacementExpected() method doesn't provide data in the correct format. + */ + public static function dataTabReplacement() + { + $data = [ + 'Tab indentation' => [ + 'testMarker' => '/* testTabIndentation */', + 'testTarget' => T_WHITESPACE, + ], + 'Mixed tab/space indentation' => [ + 'testMarker' => '/* testMixedIndentation */', + 'testTarget' => T_WHITESPACE, + ], + 'Inline: single tab in text string' => [ + 'testMarker' => '/* testInlineSingleTab */', + 'testTarget' => T_CONSTANT_ENCAPSED_STRING, + ], + 'Inline: single tab between each word in text string' => [ + 'testMarker' => '/* testInlineSingleTabBetweenEachWord */', + 'testTarget' => T_DOUBLE_QUOTED_STRING, + ], + 'Inline: multiple tabs in heredoc' => [ + 'testMarker' => '/* testInlineMultiTab */', + 'testTarget' => T_HEREDOC, + ], + 'Inline: multiple tabs between each word in nowdoc' => [ + 'testMarker' => '/* testInlineMultipleTabsBetweenEachWord */', + 'testTarget' => T_NOWDOC, + ], + 'Inline: mixed spaces/tabs in text string' => [ + 'testMarker' => '/* testInlineMixedSpacesTabs */', + 'testTarget' => T_CONSTANT_ENCAPSED_STRING, + ], + 'Inline: mixed spaces/tabs between each word in text string' => [ + 'testMarker' => '/* testInlineMixedSpacesTabsBetweenEachWord */', + 'testTarget' => T_DOUBLE_QUOTED_STRING, + ], + 'Inline: tab becomes single space in comment (with tabwidth 4)' => [ + 'testMarker' => '/* testInlineSize1 */', + 'testTarget' => T_COMMENT, + ], + 'Inline: tab becomes 2 spaces in comment (with tabwidth 4)' => [ + 'testMarker' => '/* testInlineSize2 */', + 'testTarget' => T_COMMENT, + ], + 'Inline: tab becomes 3 spaces in doc comment string (with tabwidth 4)' => [ + 'testMarker' => '/* testInlineSize3 */', + 'testTarget' => T_DOC_COMMENT_STRING, + ], + 'Inline: tab becomes 4 spaces in comment (with tabwidth 4)' => [ + 'testMarker' => '/* testInlineSize4 */', + 'testTarget' => T_COMMENT, + ], + ]; + + $expectations = static::getTabReplacementExpected(); + + foreach ($data as $key => $value) { + if (isset($expectations[$key]) === false || is_array($expectations[$key]) === false) { + throw new Exception( + sprintf('Invalid getTabReplacementExpected() method. Missing expectation array for the "%s" test case', $key) + ); + } + + if (isset($expectations[$key]['length'], $expectations[$key]['content']) === false + || array_key_exists('orig_content', $expectations[$key]) === false + ) { + throw new Exception( + sprintf('Invalid expectation array for the "%s" test case. The array must contain the "length", "content" and "orig_content" keys', $key) + ); + } + + $data[$key]['expected'] = $expectations[$key]; + } + + // Set offset for test cases targetting whitespace. + $data['Tab indentation']['offset'] = 1; + $data['Mixed tab/space indentation']['offset'] = 1; + + return $data; + + }//end dataTabReplacement() + + + /** + * Data provider helper. + * + * Should be declared in child classes to set the expectations for the token array. + * + * @see dataTabReplacement() + * + * @return array> + */ + abstract public static function getTabReplacementExpected(); + + +}//end class