Browse Source

MethodArgumentSpaceFixer - add ensure_fully_multiline option

Grégoire Paris 8 years ago
parent
commit
6877da029c

+ 1 - 0
.php_cs.dist

@@ -23,6 +23,7 @@ return PhpCsFixer\Config::create()
         'header_comment' => ['header' => $header],
         'heredoc_to_nowdoc' => true,
         'list_syntax' => ['syntax' => 'long'],
+        'method_argument_space' => ['ensure_fully_multiline' => true],
         'no_extra_consecutive_blank_lines' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block'],
         'no_short_echo_tag' => true,
         'no_unreachable_default_argument_value' => true,

+ 6 - 1
README.rst

@@ -541,10 +541,15 @@ Choose from the list of available rules:
 * **method_argument_space** [@PSR2, @Symfony]
 
   In method arguments and method call, there MUST NOT be a space before
-  each comma and there MUST be one space after each comma.
+  each comma and there MUST be one space after each comma. Argument lists
+  MAY be split across multiple lines, where each subsequent line is
+  indented once. When doing so, the first item in the list MUST be on the
+  next line, and there MUST be only one argument per line.
 
   Configuration options:
 
+  - ``ensure_fully_multiline`` (``bool``): ensure every argument of a multiline
+    argument list is on its own line; defaults to ``false``
   - ``keep_multiple_spaces_after_comma`` (``bool``): whether keep multiple spaces
     after comma; defaults to ``false``
 

+ 2 - 2
src/Cache/Cache.php

@@ -58,8 +58,8 @@ final class Cache implements CacheInterface
         if (!is_int($hash)) {
             throw new \InvalidArgumentException(sprintf(
                 'Value needs to be an integer, got "%s".',
-                is_object($hash) ? get_class($hash) : gettype($hash))
-            );
+                is_object($hash) ? get_class($hash) : gettype($hash)
+            ));
         }
 
         $this->hashes[$file] = $hash;

+ 3 - 1
src/Console/Command/DescribeCommand.php

@@ -106,7 +106,9 @@ final class DescribeCommand extends Command
 
             throw new \InvalidArgumentException(sprintf(
                 '%s %s not found.%s',
-                ucfirst($e->getType()), $name, null === $alternative ? '' : ' Did you mean "'.$alternative.'"?'
+                ucfirst($e->getType()),
+                $name,
+                null === $alternative ? '' : ' Did you mean "'.$alternative.'"?'
             ));
         }
     }

+ 2 - 1
src/Console/Command/SelfUpdateCommand.php

@@ -43,7 +43,8 @@ final class SelfUpdateCommand extends Command
                 ]
             )
             ->setDescription('Update php-cs-fixer.phar to the latest stable version.')
-            ->setHelp(<<<'EOT'
+            ->setHelp(
+                <<<'EOT'
 The <info>%command.name%</info> command replace your php-cs-fixer.phar by the
 latest version released on:
 <comment>https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases</comment>

+ 2 - 2
src/Fixer/CastNotation/ModernizeTypesCastingFixer.php

@@ -37,8 +37,8 @@ final class ModernizeTypesCastingFixer extends AbstractFunctionReferenceFixer
     $a = doubleval($b);
     $a = strval ($b);
     $a = boolval($b);
-'),
-            ],
+'
+            )],
             null,
             'Risky if any of the functions `intval`, `floatval`, `doubleval`, `strval` or `boolval` are overridden.'
         );

+ 165 - 4
src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php

@@ -14,6 +14,7 @@ namespace PhpCsFixer\Fixer\FunctionNotation;
 
 use PhpCsFixer\AbstractFixer;
 use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
+use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
 use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
 use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
 use PhpCsFixer\FixerDefinition\CodeSample;
@@ -27,7 +28,7 @@ use PhpCsFixer\Tokenizer\Tokens;
  *
  * @author Kuanhung Chen <ericj.tw@gmail.com>
  */
-final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface
+final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, WhitespacesAwareFixerInterface
 {
     /**
      * Method to insert space after comma and remove space before comma.
@@ -47,7 +48,7 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
     public function getDefinition()
     {
         return new FixerDefinition(
-            'In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma.',
+            'In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line.',
             [
                 new CodeSample(
                     "<?php\nfunction sample(\$a=10,\$b=20,\$c=30) {}\nsample(1,  2);",
@@ -61,6 +62,24 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
                     "<?php\nfunction sample(\$a=10,\$b=20,\$c=30) {}\nsample(1,  2);",
                     ['keep_multiple_spaces_after_comma' => true]
                 ),
+                new CodeSample(
+                    "<?php\nfunction sample(\$a=10,\n    \$b=20,\$c=30) {}\nsample(1,\n    2);",
+                    ['ensure_fully_multiline' => true]
+                ),
+                new CodeSample(
+                    "<?php\nfunction sample(\$a=10,\n    \$b=20,\$c=30) {}\nsample(1,  \n    2);\nsample('foo',    'foobarbaz', 'baz');\nsample('foobar', 'bar',       'baz');",
+                    [
+                        'ensure_fully_multiline' => true,
+                        'keep_multiple_spaces_after_comma' => true,
+                    ]
+                ),
+                new CodeSample(
+                    "<?php\nfunction sample(\$a=10,\n    \$b=20,\$c=30) {}\nsample(1,  \n    2);\nsample('foo',    'foobarbaz', 'baz');\nsample('foobar', 'bar',       'baz');",
+                    [
+                        'ensure_fully_multiline' => true,
+                        'keep_multiple_spaces_after_comma' => false,
+                    ]
+                ),
             ]
         );
     }
@@ -81,8 +100,16 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
         for ($index = $tokens->count() - 1; $index > 0; --$index) {
             $token = $tokens[$index];
 
-            if ($token->equals('(') && !$tokens[$index - 1]->isGivenKind(T_ARRAY)) {
-                $this->fixFunction($tokens, $index);
+            if ($token->equals('(')) {
+                $meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)];
+                if (!$meaningfulTokenBeforeParenthesis->isKeyword()
+                    || $meaningfulTokenBeforeParenthesis->isGivenKind([T_LIST, T_FUNCTION])) {
+                    if ($this->fixFunction($tokens, $index) && $this->configuration['ensure_fully_multiline']) {
+                        if (!$meaningfulTokenBeforeParenthesis->isGivenKind(T_LIST)) {
+                            $this->ensureFunctionFullyMultiline($tokens, $index);
+                        }
+                    }
+                }
             }
         }
     }
@@ -97,6 +124,13 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
                 ->setAllowedTypes(['bool'])
                 ->setDefault(false)
                 ->getOption(),
+            (new FixerOptionBuilder(
+                'ensure_fully_multiline',
+                'Ensure every argument of a multiline argument list is on its own line'
+            ))
+                ->setAllowedTypes(['bool'])
+                ->setDefault(false) // @TODO should be true at 3.0
+                ->getOption(),
         ]);
     }
 
@@ -105,10 +139,14 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
      *
      * @param Tokens $tokens             Tokens to handle
      * @param int    $startFunctionIndex Start parenthesis position
+     *
+     * @return bool whether the function is multiline
      */
     private function fixFunction(Tokens $tokens, $startFunctionIndex)
     {
         $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex);
+        $isMultiline = $this->isNewline($tokens[$startFunctionIndex + 1])
+            || $this->isNewline($tokens[$endFunctionIndex - 1]);
 
         for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) {
             $token = $tokens[$index];
@@ -127,8 +165,119 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
 
             if ($token->equals(',')) {
                 $this->fixSpace2($tokens, $index);
+                if (!$isMultiline && $this->isNewline($tokens[$index + 1])) {
+                    $isMultiline = true;
+                    break;
+                }
+            }
+        }
+
+        return $isMultiline;
+    }
+
+    private function ensureFunctionFullyMultiline(Tokens $tokens, $startFunctionIndex)
+    {
+        // find out what the indentation is
+        $searchIndex = $startFunctionIndex;
+        do {
+            $prevWhitespaceTokenIndex = $tokens->getPrevTokenOfKind(
+                $searchIndex,
+                [[T_WHITESPACE]]
+            );
+            $searchIndex = $prevWhitespaceTokenIndex;
+        } while ($prevWhitespaceTokenIndex
+            && false === strpos($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n")
+        );
+        $existingIndentation = $prevWhitespaceTokenIndex
+            ? ltrim($tokens[$prevWhitespaceTokenIndex]->getContent(), "\n\r")
+            : '';
+
+        $indentation = $existingIndentation.$this->whitespacesConfig->getIndent();
+        $endFunctionIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $startFunctionIndex);
+        if (!$this->isNewline($tokens[$endFunctionIndex - 1])) {
+            $this->addNewlineAndIndent(
+                $tokens,
+                $endFunctionIndex,
+                $existingIndentation,
+                false
+            );
+            ++$endFunctionIndex;
+        }
+
+        for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) {
+            $token = $tokens[$index];
+
+            // skip nested method calls and arrays
+            if ($token->equals(')')) {
+                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index, false);
+                continue;
+            }
+
+            // skip nested arrays
+            if ($token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE)) {
+                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $index, false);
+                continue;
+            }
+
+            if ($token->equals(',')) {
+                $this->fixNewline($tokens, $index, $indentation);
             }
         }
+        $this->fixNewLine($tokens, $startFunctionIndex, $indentation, false);
+    }
+
+    /**
+     * Method to insert newline after comma or opening parenthesis.
+     *
+     * @param Tokens $tokens
+     * @param int    $index       index of a comma
+     * @param string $indentation the indentation that should be used
+     * @param bool   $override    whether to override the existing character or not
+     */
+    private function fixNewline(Tokens $tokens, $index, $indentation, $override = true)
+    {
+        if ($this->isNewline($tokens[$index + 1]) || $tokens[$index + 1]->isComment()) {
+            return;
+        }
+        if ($tokens[$index + 2]->isComment()) {
+            $nextMeaningfulTokenIndex = $tokens->getNextMeaningfulToken($index + 2);
+            if (!$this->isNewLine($tokens[$nextMeaningfulTokenIndex - 1])) {
+                $this->addNewlineAndIndent(
+                    $tokens,
+                    $nextMeaningfulTokenIndex,
+                    $indentation,
+                    false
+                );
+            }
+
+            return;
+        }
+
+        $this->addNewlineAndIndent($tokens, $index + 1, $indentation, $override);
+    }
+
+    /**
+     * Makes sure there is a whitespace at the given location.
+     *
+     * @param Tokens $tokens      The token stream to modify
+     * @param int    $index       where to insert the whitespace
+     * @param string $indentation the indentation that should be used
+     * @param bool   $override    whether to override the existing character or not
+     */
+    private function addNewlineAndIndent(Tokens $tokens, $index, $indentation, $override)
+    {
+        $whitespaceToken = new Token([
+            T_WHITESPACE,
+            $this->whitespacesConfig->getLineEnding().$indentation,
+        ]);
+
+        if ($override) {
+            $tokens[$index] = $whitespaceToken;
+
+            return;
+        }
+
+        $tokens->insertAt($index, $whitespaceToken);
     }
 
     /**
@@ -191,4 +340,16 @@ final class MethodArgumentSpaceFixer extends AbstractFixer implements Configurat
 
         return $content !== ltrim($content, "\r\n");
     }
+
+    /**
+     * Checks if token is new line.
+     *
+     * @param Token $token
+     *
+     * @return bool
+     */
+    private function isNewLine(Token $token)
+    {
+        return $token->isWhitespace() && false !== strpos($token->getContent(), "\n");
+    }
 }

+ 2 - 1
src/Fixer/PhpUnit/PhpUnitFqcnAnnotationFixer.php

@@ -74,7 +74,8 @@ final class MyTest extends \PHPUnit_Framework_TestCase
         foreach ($tokens as $index => $token) {
             if ($token->isGivenKind(T_DOC_COMMENT)) {
                 $tokens[$index] = new Token([T_DOC_COMMENT, preg_replace(
-                    '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(\w.*)$~m', '$1\\\\$2',
+                    '~^(\s*\*\s*@(?:expectedException|covers|coversDefaultClass|uses)\h+)(\w.*)$~m',
+                    '$1\\\\$2',
                     $token->getContent()
                 )]);
             }

+ 2 - 1
src/Fixer/Phpdoc/PhpdocReturnSelfReferenceFixer.php

@@ -43,7 +43,8 @@ final class PhpdocReturnSelfReferenceFixer extends AbstractFixer implements Conf
     {
         return new FixerDefinition(
             'The type of `@return` annotations of methods returning a reference to itself must the configured one.',
-            [new CodeSample('
+            [new CodeSample(
+                '
 <?php
 class Sample
 {

+ 2 - 2
src/Fixer/Whitespace/IndentationTypeFixer.php

@@ -33,8 +33,8 @@ final class IndentationTypeFixer extends AbstractFixer implements WhitespacesAwa
     {
         return new FixerDefinition(
             'Code MUST use configured indentation type.',
-            [new CodeSample("<?php\n\nif (true) {\n\techo 'Hello!';\n}"),
-        ]);
+            [new CodeSample("<?php\n\nif (true) {\n\techo 'Hello!';\n}")]
+        );
     }
 
     /**

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