diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000000..0174b6a6561 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,23 @@ +version: 2 + +jobs: + build: + macos: + xcode: '9.0' + steps: + - checkout + + - save_cache: + key: dependency-cache + paths: + - ~/.composer + - ~/Library/Caches/Homebrew + + - run: brew update + - run: brew install php72 + - run: echo "memory_limit = 512M" > $(brew --prefix)/etc/php/7.2/conf.d/memory.ini + - run: curl -sS https://getcomposer.org/installer | php + - run: php composer.phar global show hirak/prestissimo -q || php composer.phar global require --no-interaction --no-progress --optimize-autoloader hirak/prestissimo + - run: php composer.phar install --optimize-autoloader --no-interaction --no-progress --no-suggest + - run: vendor/bin/phpunit + - run: PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer --diff --dry-run -v fix diff --git a/.gitattributes b/.gitattributes index a498fc2dde3..6b8e4eea1e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ tests/Test/AbstractFixerWithAliasedOptionsTestCase.php export-ignore tests/Test/AbstractTransformerTestCase.php export-ignore .appveyor.yml export-ignore +.circleci/ export-ignore .composer-require-checker.json export-ignore .editorconfig export-ignore .gitattributes export-ignore diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 5b7800569dd..00000000000 --- a/circle.yml +++ /dev/null @@ -1,25 +0,0 @@ -machine: - xcode: - version: 8.2 - -checkout: - pre: - - system_profiler SPSoftwareDataType - -dependencies: - cache_directories: - - ~/.composer - - ~/Library/Caches/Homebrew - pre: - - brew update - - brew install php72 - - echo "memory_limit = 512M" > $(brew --prefix)/etc/php/7.2/conf.d/memory.ini - - curl -sS https://getcomposer.org/installer | php - - php composer.phar global show hirak/prestissimo -q || php composer.phar global require --no-interaction --no-progress --optimize-autoloader hirak/prestissimo - override: - - php composer.phar install --optimize-autoloader --no-interaction --no-progress --no-suggest - -test: - override: - - vendor/bin/phpunit - - PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer --diff --dry-run -v fix diff --git a/src/Fixer/Casing/LowercaseStaticReferenceFixer.php b/src/Fixer/Casing/LowercaseStaticReferenceFixer.php index 38c8c3be67a..d935fcf6c72 100644 --- a/src/Fixer/Casing/LowercaseStaticReferenceFixer.php +++ b/src/Fixer/Casing/LowercaseStaticReferenceFixer.php @@ -86,7 +86,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) } $prevIndex = $tokens->getPrevMeaningfulToken($index); - if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { + if ($tokens[$prevIndex]->isGivenKind([T_CONST, T_DOUBLE_COLON, T_FUNCTION, T_NAMESPACE, T_NS_SEPARATOR, T_OBJECT_OPERATOR, T_PRIVATE, T_PROTECTED, T_PUBLIC])) { continue; } diff --git a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php index 207b7c99495..7f9dac7abb0 100644 --- a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php +++ b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php @@ -70,6 +70,16 @@ public function doSomething(\Foo\Bar $foo): \Foo\Bar\Baz ); } + /** + * {@inheritdoc} + */ + public function getPriority() + { + // should run after PhpdocToReturnTypeFixer + // should run before NoSuperfluousPhpdocTagsFixer + return 7; + } + /** * {@inheritdoc} */ diff --git a/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php b/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php index c13888f1871..6d94fddd26b 100644 --- a/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php +++ b/src/Fixer/PhpUnit/PhpUnitInternalClassFixer.php @@ -256,7 +256,7 @@ private function splitUpDocBlock($lines, Tokens $tokens, $docBlockIndex) } /** - * @param Line []$line + * @param Line[] $line * * @return string */ diff --git a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php index 26b9cbfb443..f7fda46c5b7 100644 --- a/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php +++ b/src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php @@ -139,7 +139,7 @@ private function findDocumentedFunction(Tokens $tokens, $index) do { $index = $tokens->getNextMeaningfulToken($index); - if ($tokens[$index]->isGivenKind(T_FUNCTION)) { + if (null === $index || $tokens[$index]->isGivenKind(T_FUNCTION)) { return $index; } } while ($tokens[$index]->isGivenKind([T_ABSTRACT, T_FINAL, T_STATIC, T_PRIVATE, T_PROTECTED, T_PUBLIC])); @@ -147,6 +147,13 @@ private function findDocumentedFunction(Tokens $tokens, $index) return null; } + /** + * @param Tokens $tokens + * @param int $start + * @param int $end + * + * @return array + */ private function getArgumentsInfo(Tokens $tokens, $start, $end) { $argumentsInfo = []; @@ -226,6 +233,13 @@ private function parseTypeHint(Tokens $tokens, $index) ]; } + /** + * @param Annotation $annotation + * @param array $info + * @param array $symbolShortNames + * + * @return bool + */ private function annotationIsSuperfluous(Annotation $annotation, array $info, array $symbolShortNames) { if ('param' === $annotation->getTag()->getName()) { @@ -244,7 +258,7 @@ private function annotationIsSuperfluous(Annotation $annotation, array $info, ar return true; } - $actualTypes = [$info['type']]; + $actualTypes = null === $info['type'] ? [] : [$info['type']]; if ($info['allows_null']) { $actualTypes[] = 'null'; } @@ -258,8 +272,8 @@ private function annotationIsSuperfluous(Annotation $annotation, array $info, ar * Converts given types to lowercase, replaces imports aliases with * their matching FQCN, and finally sorts the result. * - * @param array $types The types to normalize - * @param array $symbolShortNames The imports aliases + * @param string[] $types The types to normalize + * @param array $symbolShortNames The imports aliases * * @return array The normalized types */ diff --git a/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php b/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php index 0db5a293a29..196ef2a0234 100644 --- a/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php +++ b/src/Fixer/Phpdoc/PhpdocAnnotationWithoutDotFixer.php @@ -78,14 +78,14 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $content = $annotation->getContent(); if ( - 1 !== Preg::match('/[.。]$/u', $content) - || 0 !== Preg::match('/[.。](?!$)/u', $content, $matches) + 1 !== Preg::match('/[.。]\h*$/u', $content) + || 0 !== Preg::match('/[.。](?!\h*$)/u', $content, $matches) ) { continue; } $endLine = $doc->getLine($annotation->getEnd()); - $endLine->setContent(Preg::replace('/(?getContent())); + $endLine->setContent(Preg::replace('/(?getContent())); $startLine = $doc->getLine($annotation->getStart()); $optionalTypeRegEx = $annotation->supportTypes() diff --git a/src/Fixer/Strict/StrictParamFixer.php b/src/Fixer/Strict/StrictParamFixer.php index f0e944e4ac3..cf4fc957100 100644 --- a/src/Fixer/Strict/StrictParamFixer.php +++ b/src/Fixer/Strict/StrictParamFixer.php @@ -77,7 +77,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) $previousIndex = $tokens->getPrevMeaningfulToken($index); if (null !== $previousIndex && $tokens[$previousIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { - return; + continue; } $lowercaseContent = strtolower($token->getContent()); diff --git a/src/Fixer/StringNotation/ExplicitStringVariableFixer.php b/src/Fixer/StringNotation/ExplicitStringVariableFixer.php index dca33542ec7..ee205c5c120 100644 --- a/src/Fixer/StringNotation/ExplicitStringVariableFixer.php +++ b/src/Fixer/StringNotation/ExplicitStringVariableFixer.php @@ -95,6 +95,12 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens) foreach ($variableTokens as $variablePartIndex => $variablePartToken) { if ($variablePartToken->isGivenKind(T_NUM_STRING)) { $tokens[$variablePartIndex] = new Token([T_LNUMBER, $variablePartToken->getContent()]); + + continue; + } + + if ($variablePartToken->isGivenKind(T_STRING) && $tokens[$variablePartIndex + 1]->equals(']')) { + $tokens[$variablePartIndex] = new Token([T_CONSTANT_ENCAPSED_STRING, "'".$variablePartToken->getContent()."'"]); } } diff --git a/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php b/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php index 2dc7acb00d9..f1894f92338 100644 --- a/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php +++ b/src/Fixer/Whitespace/NoTrailingWhitespaceFixer.php @@ -62,17 +62,37 @@ public function isCandidate(Tokens $tokens) */ protected function applyFix(\SplFileInfo $file, Tokens $tokens) { - foreach ($tokens as $index => $token) { + for ($index = count($tokens) - 1; $index >= 0; --$index) { + $token = $tokens[$index]; + if ( + $token->isGivenKind(T_OPEN_TAG) + && $tokens->offsetExists($index + 1) + && $tokens[$index + 1]->isWhitespace() + && 1 === Preg::match('/(.*)\h$/', $token->getContent(), $openTagMatches) + && 1 === Preg::match('/^(\R)(.*)$/s', $tokens[$index + 1]->getContent(), $whitespaceMatches) + ) { + $tokens[$index] = new Token([T_OPEN_TAG, $openTagMatches[1].$whitespaceMatches[1]]); + if ('' === $whitespaceMatches[2]) { + $tokens->clearAt($index + 1); + } else { + $tokens[$index + 1] = new Token([T_WHITESPACE, $whitespaceMatches[2]]); + } + + continue; + } + if (!$token->isWhitespace()) { continue; } - $lines = Preg::split("/([\r\n]+)/", $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); + $lines = Preg::split('/(\\R+)/', $token->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); $linesSize = count($lines); // fix only multiline whitespaces or singleline whitespaces at the end of file if ($linesSize > 1 || !isset($tokens[$index + 1])) { - $lines[0] = rtrim($lines[0], " \t"); + if (!$tokens[$index - 1]->isGivenKind(T_OPEN_TAG) || 1 !== Preg::match('/(.*)\R$/', $tokens[$index - 1]->getContent())) { + $lines[0] = rtrim($lines[0], " \t"); + } for ($i = 1; $i < $linesSize; ++$i) { $trimmedLine = rtrim($lines[$i], " \t"); diff --git a/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php b/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php index 460bfd36795..a75dd6e7a21 100644 --- a/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php +++ b/src/Tokenizer/Analyzer/ArgumentsAnalyzer.php @@ -107,7 +107,7 @@ public function getArgumentInfo(Tokens $tokens, $argumentStart, $argumentEnd) $sawName = false; for ($index = $argumentStart; $index <= $argumentEnd; ++$index) { $token = $tokens[$index]; - if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS)) { + if ($token->isComment() || $token->isWhitespace() || $token->isGivenKind(T_ELLIPSIS) || $token->equals('&')) { continue; } if ($token->isGivenKind(T_VARIABLE)) { diff --git a/tests/AutoReview/FixerFactoryTest.php b/tests/AutoReview/FixerFactoryTest.php index 57a3d3622bf..adee9aea032 100644 --- a/tests/AutoReview/FixerFactoryTest.php +++ b/tests/AutoReview/FixerFactoryTest.php @@ -83,6 +83,7 @@ public function provideFixersPriorityCases() [$fixers['elseif'], $fixers['braces']], [$fixers['escape_implicit_backslashes'], $fixers['heredoc_to_nowdoc']], [$fixers['escape_implicit_backslashes'], $fixers['single_quote']], + [$fixers['fully_qualified_strict_types'], $fixers['no_superfluous_phpdoc_tags']], [$fixers['function_to_constant'], $fixers['native_function_casing']], [$fixers['function_to_constant'], $fixers['no_extra_blank_lines']], [$fixers['function_to_constant'], $fixers['no_singleline_whitespace_before_semicolons']], diff --git a/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php b/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php index 56bdab27a3d..192a694e8d6 100644 --- a/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php +++ b/tests/Fixer/Casing/LowercaseStaticReferenceFixerTest.php @@ -137,6 +137,12 @@ public function provideFixCases() [ ' [ + 'no typehint' => [ ' [ + 'same typehint' => [ ' [ + 'same optional typehint' => [ ' [ + 'same typehint with description' => [ ' [ + 'no typehint mixed' => [ ' [ + 'multiple different types' => [ ' [ + 'same typehint with different casing' => [ ' [ + 'multiple arguments' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with unmapped param' => [ ' [ + 'with param superfluous but not return' => [ ' [ + 'with not all params superfluous' => [ ' [ + 'with special type hints' => [ ' [ + ' [ + 'same type hint' => [ ' [ + 'same type hint with description' => [ ' [ + 'multiple different types' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with scalar type hints' => [ ' [ + 'same nullable type hint' => [ ' [ + 'same nullable type hint reversed' => [ ' [ + 'same nullable type hint with description' => [ ' [ + 'same optional nullable type hint' => [ ' [ + 'multiple different types' => [ ' [ + 'with import' => [ ' [ + 'with root symbols' => [ ' [ + 'with mix of imported and fully qualified symbols' => [ ' [ + 'with aliased imported' => [ ' [ + 'with special type hints' => [ ' $expectedToken) { - $option = ['JSON_PRETTY_PRINT']; - $inputToken = $inputTokens[$index]; - - $this->assertTrue( - $expectedToken->equals($inputToken), - sprintf("The token at index %d must be:\n%s,\ngot:\n%s.", $index, $expectedToken->toJson($option), $inputToken->toJson($option)) - ); - - $expectedTokenKind = $expectedToken->isArray() ? $expectedToken->getId() : $expectedToken->getContent(); - $this->assertTrue( - $inputTokens->isTokenKindFound($expectedTokenKind), - sprintf('The token kind %s must be found in fixed tokens collection.', $expectedTokenKind) - ); - } - - $this->assertSame($expectedTokens->count(), $inputTokens->count(), 'Both collections must have the same length.'); - } - /** * @return LinterInterface */ diff --git a/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php b/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php index f167bbde681..fe6111b90a3 100644 --- a/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php +++ b/tests/Tokenizer/Analyzer/ArgumentsAnalyzerTest.php @@ -116,6 +116,16 @@ public function provideArgumentsInfoCases() 3 ) )], + ['