|
@@ -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");
|
|
|
+ }
|
|
|
}
|