diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e0d3c40..6e0ce47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,11 +32,22 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + - name: Setup ini config + id: set_ini + run: | + # On PHP 5.3, short_open_tag needs to be turned on for short open echo tags to be recognized + # a PHP tags. As this only affects PHP 5.3, this is not something of serious concern. + if [ ${{ matrix.php }} == "5.3" ]; then + echo '::set-output name=PHP_INI::zend.assertions=1, error_reporting=-1, display_errors=On, short_open_tag=On' + else + echo '::set-output name=PHP_INI::zend.assertions=1, error_reporting=-1, display_errors=On' + fi + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - ini-values: zend.assertions=1, error_reporting=-1, display_errors=On + ini-values: ${{ steps.set_ini.outputs.PHP_INI }} coverage: none tools: cs2pr diff --git a/composer.json b/composer.json index 269855d..fc7fe76 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php-parallel-lint/php-console-color": "^1.0.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0", "php-parallel-lint/php-parallel-lint": "^1.0", "php-parallel-lint/php-var-dump-check": "0.*", "php-parallel-lint/php-code-style": "^2.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c236588..355e3e8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,9 +1,18 @@ @@ -19,9 +28,9 @@ - + diff --git a/tests/HighlighterTest.php b/tests/HighlighterTest.php deleted file mode 100644 index 4c1fb36..0000000 --- a/tests/HighlighterTest.php +++ /dev/null @@ -1,363 +0,0 @@ -createMock('\PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor') - : $this->getMock('\PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor'); - - $mock->expects($this->any()) - ->method('apply') - ->will($this->returnCallback(function ($style, $text) { - return "<$style>$text"; - })); - - $mock->expects($this->any()) - ->method('hasTheme') - ->will($this->returnValue(true)); - - return $mock; - } - - /** - * @before - */ - protected function setUpHighlighter() - { - $this->uut = new Highlighter($this->getConsoleColorMock()); - } - - protected function compare($original, $expected) - { - $output = $this->uut->getWholeFile($original); - // Allow unit tests to succeed on non-*nix systems. - $output = str_replace(array("\r\n", "\r"), "\n", $output); - - $this->assertSame($expected, $output); - } - - public function testVariable() - { - $this->compare( - << -echo \$a; -EOL - ); - } - - public function testInteger() - { - $this->compare( - << -echo 43; -EOL - ); - } - - public function testFloat() - { - $this->compare( - << -echo 43.3; -EOL - ); - } - - public function testHex() - { - $this->compare( - << -echo 0x43; -EOL - ); - } - - public function testBasicFunction() - { - $this->compare( - << -function plus(\$a, \$b) { - return \$a + \$b; -} -EOL - ); - } - - public function testStringNormal() - { - $this->compare( - << -echo 'Ahoj světe'; -EOL - ); - } - - public function testStringDouble() - { - $this->compare( - << -echo "Ahoj světe"; -EOL - ); - } - - public function testInstanceof() - { - $this->compare( - << -\$a instanceof stdClass; -EOL - ); - } - - /* - * Constants - */ - public function testConstant() - { - $constants = array( - '__FILE__', - '__LINE__', - '__CLASS__', - '__FUNCTION__', - '__METHOD__', - '__TRAIT__', - '__DIR__', - '__NAMESPACE__' - ); - - foreach ($constants as $constant) { - $this->compare( - << -$constant; -EOL - ); - } - } - - /* - * Comments - */ - public function testComment() - { - $this->compare( - << -/* Ahoj */ -EOL - ); - } - - public function testDocComment() - { - $this->compare( - << -/** Ahoj */ -EOL - ); - } - - public function testInlineComment() - { - $this->compare( - << -// Ahoj -EOL - ); - } - - public function testHashComment() - { - $this->compare( - << -# Ahoj -EOL - ); - } - - public function testEmpty() - { - $this->compare( - '', - '' - ); - } - - public function testWhitespace() - { - $this->compare( - ' ', - ' ' - ); - } - - public function testFunctionCall() - { - $this->compare( - <<<'EOL' - -echo functionName(); -EOL - ); - } - - public function testFQNFunctionCall() - { - $original = <<<'EOL' - -echo \My\Package\functionName(); -EOL; - } else { - $expected = <<<'EOL' - -echo \My\Package\functionName(); -EOL; - } - - $this->compare($original, $expected); - } - - public function testNamespaceRelativeFunctionCall() - { - $original = <<<'EOL' - -echo namespace\functionName(); -EOL; - } else { - $expected = <<<'EOL' - -echo namespace\functionName(); -EOL; - } - - $this->compare($original, $expected); - } - - public function testQualifiedFunctionCall() - { - $original = <<<'EOL' - -echo Package\functionName(); -EOL; - } else { - $expected = <<<'EOL' - -echo Package\functionName(); -EOL; - } - - $this->compare($original, $expected); - } -} diff --git a/tests/TokenizeTest.php b/tests/TokenizeTest.php new file mode 100644 index 0000000..0170bc8 --- /dev/null +++ b/tests/TokenizeTest.php @@ -0,0 +1,760 @@ +uut = new Highlighter($this->getConsoleColorMock()); + } + + /** + * Helper method mocking the Console Color Class. + * + * @return \PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor + */ + protected function getConsoleColorMock() + { + $mock = method_exists($this, 'createMock') + ? $this->createMock('\PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor') + : $this->getMock('\PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor'); + + $mock->expects($this->any()) + ->method('apply') + ->will($this->returnCallback(function ($style, $text) { + return "<$style>$text"; + })); + + $mock->expects($this->any()) + ->method('hasTheme') + ->will($this->returnValue(true)); + + return $mock; + } + + /** + * Helper method executing the actual tests. + * + * {@internal This is a work-around to allow for supporting PHPUnit 4.x. + * In PHPUnit 5 and higher, a test method can have multiple data provider tags and + * will execute the test cases for each. Unfortunately, that wasn't supported in PHPUnit 4.x. + * This effectively means that each data provider needs its own test method for now.} + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + protected function compare($original, $expected) + { + $output = $this->uut->getWholeFile($original); + // Allow unit tests to succeed on non-*nix systems. + $output = str_replace(array("\r\n", "\r"), "\n", $output); + + $this->assertSame($expected, $output); + } + + /** + * Test the tokenizer and token specific highlighting of "empty" inputs. + * + * @dataProvider dataEmptyFiles + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testEmptyFiles($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataEmptyFiles() + { + return array( + 'Empty file' => array( + 'original' => '', + 'expected' => '', + ), + 'File only containing whitespace' => array( + 'original' => ' ', + 'expected' => ' ', + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of PHP tag tokens. + * + * @dataProvider dataPhpTags + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testPhpTags($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataPhpTags() + { + return array( + '"Long" open tag with close tag' => array( + 'original' => <<<'EOL' + +EOL + , + 'expected' => <<<'EOL' +echo PHP_EOL; ?> +EOL + ), + 'Short open tag with close tag' => array( + 'original' => <<<'EOL' +text more text +EOL + , + 'expected' => <<<'EOL' +text /* comment */ ?> more text +EOL + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of the magic constants. + * + * @dataProvider dataMagicConstants + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testMagicConstants($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataMagicConstants() + { + $magicConstants = array( + '__FILE__', + '__LINE__', + '__CLASS__', + '__FUNCTION__', + '__METHOD__', + '__TRAIT__', + '__DIR__', + '__NAMESPACE__' + ); + + $data = array(); + foreach ($magicConstants as $constant) { + $data['Magic constant: ' . $constant] = array( + 'original' => << << +$constant; +EOL + ); + } + + return $data; + } + + /** + * Test the tokenizer and token specific highlighting of the "miscellaneous" tokens. + * + * @dataProvider dataMiscTokens + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testMiscTokens($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataMiscTokens() + { + return array( + 'Constant (T_STRING)' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo PHP_EOL; +EOL + ), + 'Variable' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo $a; +EOL + ), + 'Integer: decimal' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo 43; +EOL + ), + 'Integer: hexadecimal' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo 0x43; +EOL + ), + 'Float' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo 43.3; +EOL + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of comment tokens. + * + * @dataProvider dataComments + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testComments($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataComments() + { + return array( + 'Doc block: single line' => array( + 'original' => <<<'EOL' + <<<'EOL' + +/** Ahoj */ +EOL + ), + 'Doc block: multi line' => array( + 'original' => <<<'EOL' + <<<'EOL' + +/** + * Ahoj + * + * @param type $name Description + */ +EOL + ), + 'Star comment: single line' => array( + 'original' => <<<'EOL' + <<<'EOL' + +/* Ahoj */ +EOL + ), + 'Star comment: multi line' => array( + 'original' => <<<'EOL' + <<<'EOL' + +/* + Ahoj + */ +EOL + ), + 'Slash comment' => array( + 'original' => <<<'EOL' + <<<'EOL' + +// Ahoj +EOL + ), + 'Slash comment, multiple' => array( + 'original' => <<<'EOL' + <<<'EOL' + +// Ahoj +// Ahoj again +EOL + ), + 'Hash comment' => array( + 'original' => <<<'EOL' + <<<'EOL' + +# Ahoj +EOL + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of text string tokens. + * + * @dataProvider dataTextStrings + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testTextStrings($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider for testing text string tokens. + * + * @return array + */ + public function dataTextStrings() + { + return array( + 'Single quoted text string' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo 'Ahoj světe'; +EOL + ), + 'Double quoted text string' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo "Ahoj světe"; +EOL + ), + 'Double quoted text string with interpolation [1]' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo "Ahoj $text and more text"; +EOL + ), + 'Double quoted text string with interpolation [2]' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo "$text and more text"; +EOL + ), + 'Double quoted text string with interpolation [3]' => array( + 'original' => <<<'EOL' +prop} and more text"; +EOL + , + 'expected' => <<<'EOL' + +echo "Ahoj {$obj->prop} and more text"; +EOL + ), + 'Nowdoc' => array( + 'original' => ' <<<'EOL' + +echo <<<'TXT' +Text +TXT; + +EOL + ), + 'Heredoc' => array( + 'original' => ' <<<'EOL' + +echo << +Text +TXT; + +EOL + ), + 'Heredoc with interpolation' => array( + 'original' => ' <<<'EOL' + +echo << +Text $text +TXT; + +EOL + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of inline HTML tokens. + * + * @dataProvider dataInlineHtml + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testInlineHtml($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataInlineHtml() + { + return array( + 'Inline HTML' => array( + 'original' => <<<'EOL' +
+EOL + , + 'expected' => <<<'EOL' +
+EOL + ), + ); + } + + /** + * Test the tokenizer and token specific highlighting of name tokens. + * + * @dataProvider dataNameTokens + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testNameTokens($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataNameTokens() + { + $data = array( + 'Unqualified function call' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo functionName(); +EOL + ), + ); + + $data['Fully qualified function call'] = array( + 'original' => <<<'EOL' + +echo \My\Package\functionName(); +EOL; + } else { + $data['Fully qualified function call']['expected'] = <<<'EOL' + +echo \My\Package\functionName(); +EOL; + } + + $data['Namespace relative function call'] = array( + 'original' => <<<'EOL' + +echo namespace\functionName(); +EOL; + } else { + $data['Namespace relative function call']['expected'] = <<<'EOL' + +echo namespace\functionName(); +EOL; + } + + $data['Partially qualified function call'] = array( + 'original' => <<<'EOL' + +echo Package\functionName(); +EOL; + } else { + $data['Partially qualified function call']['expected'] = <<<'EOL' + +echo Package\functionName(); +EOL; + } + + return $data; + } + + /** + * Test the tokenizer and token specific highlighting of keyword and operator tokens. + * + * @dataProvider dataKeywordsAndOperators + * + * @param string $original The input string. + * @param string $expected The expected output string. + */ + public function testKeywordsAndOperators($original, $expected) + { + $this->compare($original, $expected); + } + + /** + * Data provider. + * + * @return array + */ + public function dataKeywordsAndOperators() + { + return array( + 'Keywords: instanceof' => array( + 'original' => <<<'EOL' + <<<'EOL' + +$a instanceof stdClass; +EOL + ), + 'Keywords: function, return' => array( + 'original' => <<<'EOL' + <<<'EOL' + +function plus($a, $b) { + return $a + $b; +} +EOL + ), + 'Keywords: while, empty, exit' => array( + 'original' => <<<'EOL' + <<<'EOL' + +while(empty($a)) { exit; } +EOL + ), + 'Keywords: type casts' => array( + 'original' => <<<'EOL' + <<<'EOL' + +$a = (int) (bool) $a . (string) $b; +EOL + ), + 'Keywords: new, clone' => array( + 'original' => <<<'EOL' + <<<'EOL' + +$obj = new stdClass; +$clone = clone $obj; +EOL + ), + 'Operators: arithmetic operators' => array( + 'original' => <<<'EOL' + <<<'EOL' + +echo 1 + 2 - 2 * 10 / 5 ** 1; +EOL + ), + 'Operators: assignment operators' => array( + 'original' => <<<'EOL' + <<<'EOL' + +$a = 10; +$a *= 10; +$a ^= 10; +$a ??= $b; +EOL + ), + 'Operators: comparison, boolean and logical operators' => array( + 'original' => <<<'EOL' + ''; +echo '' <=> '' and '' or ! ''; +EOL + , + 'expected' => <<<'EOL' + +echo '' === '' && '' !== ''; +echo true || '' > ''; +echo '' <=> '' and '' or ! ''; +EOL + ), + ); + } +}