Skip to content

Commit

Permalink
Add ensure_single_line option
Browse files Browse the repository at this point in the history
  • Loading branch information
julienfalque committed Mar 20, 2018
1 parent 2ac8def commit b5ea7e4
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .php_cs.dist
Expand Up @@ -47,7 +47,7 @@ $config = PhpCsFixer\Config::create()
'heredoc_to_nowdoc' => true,
'list_syntax' => ['syntax' => 'long'],
'method_chaining_indentation' => true,
'method_argument_space' => ['ensure_fully_multiline' => true],
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'multiline_comment_opening_closing' => true,
'no_extra_blank_lines' => ['tokens' => ['break', 'continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block']],
'no_null_property_initialization' => true,
Expand Down
7 changes: 5 additions & 2 deletions README.rst
Expand Up @@ -754,10 +754,13 @@ Choose from the list of available rules:

Configuration options:

- ``ensure_fully_multiline`` (``bool``): ensure every argument of a multiline
argument list is on its own line; defaults to ``false``
- ``ensure_fully_multiline`` (``bool``): (deprecated) 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``
- ``on_multiline`` (``'ensure_fully_multiline'``, ``'ensure_single_line'``, ``'ignore'``):
defines how to handle function arguments lists that contain newlines;
defaults to ``'ignore'``

* **method_chaining_indentation**

Expand Down
154 changes: 133 additions & 21 deletions src/Fixer/FunctionNotation/MethodArgumentSpaceFixer.php
Expand Up @@ -65,19 +65,23 @@ public function getDefinition()
),
new CodeSample(
"<?php\nfunction sample(\$a=10,\n \$b=20,\$c=30) {}\nsample(1,\n 2);\n",
['ensure_fully_multiline' => true]
['on_multiline' => 'ensure_fully_multiline']
),
new CodeSample(
"<?php\nfunction sample(\n \$a=10,\n \$b=20,\n \$c=30\n) {}\nsample(\n 1,\n 2\n);\n",
['on_multiline' => 'ensure_single_line']
),
new CodeSample(
"<?php\nfunction sample(\$a=10,\n \$b=20,\$c=30) {}\nsample(1, \n 2);\nsample('foo', 'foobarbaz', 'baz');\nsample('foobar', 'bar', 'baz');\n",
[
'ensure_fully_multiline' => true,
'on_multiline' => 'ensure_fully_multiline',
'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');\n",
[
'ensure_fully_multiline' => true,
'on_multiline' => 'ensure_fully_multiline',
'keep_multiple_spaces_after_comma' => false,
]
),
Expand All @@ -93,6 +97,21 @@ public function isCandidate(Tokens $tokens)
return $tokens->isTokenKindFound('(');
}

public function configure(array $configuration = null)
{
if (is_array($configuration) && array_key_exists('ensure_fully_multiline', $configuration)) {
@trigger_error('Option "ensure_fully_multiline" is deprecated and will be removed in 3.0, use option "on_multiline" instead.', E_USER_DEPRECATED);

unset($configuration['ensure_fully_multiline']);

if (!array_key_exists('on_multiline', $configuration)) {
$configuration['on_multiline'] = 'ensure_fully_multiline';
}
}

parent::configure($configuration);
}

/**
* {@inheritdoc}
*/
Expand All @@ -101,16 +120,26 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
for ($index = $tokens->count() - 1; $index > 0; --$index) {
$token = $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);
}
}
}
if (!$token->equals('(')) {
continue;
}

$meaningfulTokenBeforeParenthesis = $tokens[$tokens->getPrevMeaningfulToken($index)];
if (
$meaningfulTokenBeforeParenthesis->isKeyword()
&& !$meaningfulTokenBeforeParenthesis->isGivenKind([T_LIST, T_FUNCTION])
) {
continue;
}

$isMultiline = $this->fixFunction($tokens, $index);

if (
$isMultiline
&& 'ensure_fully_multiline' === $this->configuration['on_multiline']
&& !$meaningfulTokenBeforeParenthesis->isGivenKind(T_LIST)
) {
$this->ensureFunctionFullyMultiline($tokens, $index);
}
}
}
Expand All @@ -127,10 +156,17 @@ protected function createConfigurationDefinition()
->getOption(),
(new FixerOptionBuilder(
'ensure_fully_multiline',
'Ensure every argument of a multiline argument list is on its own line'
'(deprecated) ensure every argument of a multiline argument list is on its own line'
))
->setAllowedTypes(['bool'])
->setDefault(false) // @TODO 3.0 should be true
->setDefault(false) // @TODO 3.0 remove
->getOption(),
(new FixerOptionBuilder(
'on_multiline',
'Defines how to handle function arguments lists that contain newlines.'
))
->setAllowedValues(['ignore', 'ensure_single_line', 'ensure_fully_multiline'])
->setDefault('ignore') // @TODO 3.0 should be 'ensure_fully_multiline'
->getOption(),
]);
}
Expand All @@ -146,8 +182,28 @@ protected function createConfigurationDefinition()
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]);

$isMultiline = false;

$firstWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $startFunctionIndex, $endFunctionIndex);
$lastWhitespaceIndex = $this->findWhitespaceIndexAfterParenthesis($tokens, $endFunctionIndex, $startFunctionIndex);

foreach ([$firstWhitespaceIndex, $lastWhitespaceIndex] as $index) {
if (null === $index || !Preg::match('/\R/', $tokens[$index]->getContent())) {
continue;
}

if ('ensure_single_line' !== $this->configuration['on_multiline']) {
$isMultiline = true;

continue;
}

$newLinesRemoved = $this->ensureSingleLine($tokens, $index);
if (!$newLinesRemoved) {
$isMultiline = true;
}
}

for ($index = $endFunctionIndex - 1; $index > $startFunctionIndex; --$index) {
$token = $tokens[$index];
Expand Down Expand Up @@ -183,6 +239,57 @@ private function fixFunction(Tokens $tokens, $startFunctionIndex)
return $isMultiline;
}

/**
* @param Tokens $tokens
* @param int $startParenthesisIndex
* @param int $endParenthesisIndex
*
* @return null|int
*/
private function findWhitespaceIndexAfterParenthesis(Tokens $tokens, $startParenthesisIndex, $endParenthesisIndex)
{
$direction = $endParenthesisIndex > $startParenthesisIndex ? 1 : -1;
$startIndex = $startParenthesisIndex + $direction;
$endIndex = $endParenthesisIndex - $direction;

for ($index = $startIndex; $index !== $endIndex; $index += $direction) {
$token = $tokens[$index];

if ($token->isWhitespace()) {
return $index;
}

if (!$token->isComment()) {
break;
}
}

return null;
}

/**
* @param Tokens $tokens
* @param int $index
*
* @return bool Whether newlines were removed from the whitespace token
*/
private function ensureSingleLine(Tokens $tokens, $index)
{
$previousToken = $tokens[$index - 1];
if ($previousToken->isComment() && 0 !== strpos($previousToken->getContent(), '/*')) {
return false;
}

$content = Preg::replace('/\R[ \t]*/', '', $tokens[$index]->getContent());
if ('' !== $content) {
$tokens[$index] = new Token([T_WHITESPACE, $content]);
} else {
$tokens->clearAt($index);
}

return true;
}

private function ensureFunctionFullyMultiline(Tokens $tokens, $startFunctionIndex)
{
// find out what the indentation is
Expand Down Expand Up @@ -288,14 +395,19 @@ private function fixSpace2(Tokens $tokens, $index)
// 1) multiple spaces after comma
// 2) no space after comma
if ($nextToken->isWhitespace()) {
$newContent = $nextToken->getContent();

if ('ensure_single_line' === $this->configuration['on_multiline']) {
$newContent = Preg::replace('/\R/', '', $newContent);
}

if (
($this->configuration['keep_multiple_spaces_after_comma'] && !Preg::match('/\R/', $nextToken->getContent()))
|| $this->isCommentLastLineToken($tokens, $index + 2)
(!$this->configuration['keep_multiple_spaces_after_comma'] || Preg::match('/\R/', $newContent))
&& !$this->isCommentLastLineToken($tokens, $index + 2)
) {
return;
$newContent = ltrim($newContent, " \t");
}

$newContent = ltrim($nextToken->getContent(), " \t");
$tokens[$nextIndex] = new Token([T_WHITESPACE, '' === $newContent ? ' ' : $newContent]);

return;
Expand Down
2 changes: 1 addition & 1 deletion src/RuleSet.php
Expand Up @@ -40,7 +40,7 @@ final class RuleSet implements RuleSetInterface
'line_ending' => true,
'lowercase_constants' => true,
'lowercase_keywords' => true,
'method_argument_space' => ['ensure_fully_multiline' => true],
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'no_break_comment' => true,
'no_closing_tag' => true,
'no_spaces_after_function_name' => true,
Expand Down

0 comments on commit b5ea7e4

Please sign in to comment.