1212 * with this source code in the file LICENSE.
1313 */
1414
15+ // PhpCsFixer\Fixer\FunctionNotation
1516namespace NetteCodingStandard \Fixer \FunctionNotation ;
1617
1718use PhpCsFixer \AbstractFixer ;
1819use PhpCsFixer \Fixer \ConfigurableFixerInterface ;
20+ use PhpCsFixer \Fixer \ConfigurableFixerTrait ;
1921use PhpCsFixer \Fixer \WhitespacesAwareFixerInterface ;
2022use PhpCsFixer \FixerConfiguration \FixerConfigurationResolver ;
2123use PhpCsFixer \FixerConfiguration \FixerConfigurationResolverInterface ;
2224use PhpCsFixer \FixerConfiguration \FixerOptionBuilder ;
23- use PhpCsFixer \FixerConfiguration \InvalidOptionsForEnvException ;
2425use PhpCsFixer \FixerDefinition \CodeSample ;
2526use PhpCsFixer \FixerDefinition \FixerDefinition ;
2627use PhpCsFixer \FixerDefinition \FixerDefinitionInterface ;
27- use PhpCsFixer \FixerDefinition \VersionSpecification ;
28- use PhpCsFixer \FixerDefinition \VersionSpecificCodeSample ;
2928use PhpCsFixer \Preg ;
3029use PhpCsFixer \Tokenizer \CT ;
3130use PhpCsFixer \Tokenizer \Token ;
3231use PhpCsFixer \Tokenizer \Tokens ;
33- use Symfony \Component \OptionsResolver \Options ;
3432
3533/**
36- * Fixer for rules defined in PSR2 ¶4.4, ¶4.6.
37- *
3834 * @author Kuanhung Chen <ericj.tw@gmail.com>
35+ *
36+ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
37+ *
38+ * @phpstan-type _AutogeneratedInputConfiguration array{
39+ * after_heredoc?: bool,
40+ * attribute_placement?: 'ignore'|'same_line'|'standalone',
41+ * keep_multiple_spaces_after_comma?: bool,
42+ * on_multiline?: 'ensure_fully_multiline'|'ensure_single_line'|'ignore'
43+ * }
44+ * @phpstan-type _AutogeneratedComputedConfiguration array{
45+ * after_heredoc: bool,
46+ * attribute_placement: 'ignore'|'same_line'|'standalone',
47+ * keep_multiple_spaces_after_comma: bool,
48+ * on_multiline: 'ensure_fully_multiline'|'ensure_single_line'|'ignore'
49+ * }
3950 */
4051final class MethodArgumentSpaceFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
4152{
42- /**
43- * {@inheritdoc}
44- */
53+ /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
54+ use ConfigurableFixerTrait;
55+
4556 public function getDefinition (): FixerDefinitionInterface
4657 {
4758 return new FixerDefinition (
@@ -81,66 +92,66 @@ public function getDefinition(): FixerDefinitionInterface
8192 'keep_multiple_spaces_after_comma ' => false ,
8293 ]
8394 ),
84- new VersionSpecificCodeSample (
95+ new CodeSample (
96+ "<?php \nfunction sample(#[Foo] #[Bar] \$a=10, \n \$b=20, \$c=30) {} \nsample(1, 2); \n" ,
97+ [
98+ 'on_multiline ' => 'ensure_fully_multiline ' ,
99+ 'attribute_placement ' => 'ignore ' ,
100+ ]
101+ ),
102+ new CodeSample (
103+ "<?php \nfunction sample(#[Foo] \n #[Bar] \n \$a=10, \n \$b=20, \$c=30) {} \nsample(1, 2); \n" ,
104+ [
105+ 'on_multiline ' => 'ensure_fully_multiline ' ,
106+ 'attribute_placement ' => 'same_line ' ,
107+ ]
108+ ),
109+ new CodeSample (
110+ "<?php \nfunction sample(#[Foo] #[Bar] \$a=10, \n \$b=20, \$c=30) {} \nsample(1, 2); \n" ,
111+ [
112+ 'on_multiline ' => 'ensure_fully_multiline ' ,
113+ 'attribute_placement ' => 'standalone ' ,
114+ ]
115+ ),
116+ new CodeSample (
85117 <<<'SAMPLE'
86- <?php
87- sample(
88- <<<EOD
89- foo
90- EOD
91- ,
92- 'bar'
93- );
94-
95- SAMPLE
118+ <?php
119+ sample(
120+ <<<EOD
121+ foo
122+ EOD
123+ ,
124+ 'bar'
125+ );
126+
127+ SAMPLE
96128 ,
97- new VersionSpecification (70300 ),
98129 ['after_heredoc ' => true ]
99130 ),
100- ]
131+ ],
132+ 'This fixer covers rules defined in PSR2 ¶4.4, ¶4.6. '
101133 );
102134 }
103135
104- /**
105- * {@inheritdoc}
106- */
107136 public function isCandidate (Tokens $ tokens ): bool
108137 {
109138 return $ tokens ->isTokenKindFound ('( ' );
110139 }
111140
112- public function configure (array $ configuration ): void
113- {
114- parent ::configure ($ configuration );
115-
116- if (isset ($ configuration ['ensure_fully_multiline ' ])) {
117- $ this ->configuration ['on_multiline ' ] = $ this ->configuration ['ensure_fully_multiline ' ]
118- ? 'ensure_fully_multiline '
119- : 'ignore ' ;
120- }
121- }
122-
123141 /**
124142 * {@inheritdoc}
125143 *
126- * Must run before ArrayIndentationFixer.
127- * Must run after BracesFixer, CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, MethodChainingIndentationFixer, NoUselessSprintfFixer, PowToExponentiationFixer.
144+ * Must run before ArrayIndentationFixer, StatementIndentationFixer .
145+ * Must run after CombineNestedDirnameFixer, FunctionDeclarationFixer, ImplodeCallFixer, LambdaNotUsedImportFixer, NoMultilineWhitespaceAroundDoubleArrowFixer, NoUselessSprintfFixer, PowToExponentiationFixer, StrictParamFixer .
128146 */
129147 public function getPriority (): int
130148 {
131149 return 30 ;
132150 }
133151
134- /**
135- * {@inheritdoc}
136- */
137152 protected function applyFix (\SplFileInfo $ file , Tokens $ tokens ): void
138153 {
139- $ expectedTokens = [T_LIST , T_FUNCTION , CT ::T_USE_LAMBDA ];
140-
141- if (\PHP_VERSION_ID >= 70400 ) {
142- $ expectedTokens [] = T_FN ;
143- }
154+ $ expectedTokens = [T_LIST , T_FUNCTION , CT ::T_USE_LAMBDA , T_FN , T_CLASS ];
144155
145156 for ($ index = $ tokens ->count () - 1 ; $ index > 0 ; --$ index ) {
146157 $ token = $ tokens [$ index ];
@@ -151,23 +162,25 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
151162
152163 $ meaningfulTokenBeforeParenthesis = $ tokens [$ tokens ->getPrevMeaningfulToken ($ index )];
153164
154- if ($ meaningfulTokenBeforeParenthesis ->isGivenKind (T_STRING )) {
155- $ isMultiline = $ this ->fixFunction ($ tokens , $ index );
165+ if (
166+ $ meaningfulTokenBeforeParenthesis ->isKeyword ()
167+ && !$ meaningfulTokenBeforeParenthesis ->isGivenKind ($ expectedTokens )
168+ ) {
169+ continue ;
170+ }
156171
157- if (
158- $ isMultiline
159- && 'ensure_fully_multiline ' === $ this ->configuration ['on_multiline ' ]
160- && !$ meaningfulTokenBeforeParenthesis ->isGivenKind (T_LIST )
161- ) {
162- $ this ->ensureFunctionFullyMultiline ($ tokens , $ index );
163- }
172+ $ isMultiline = $ this ->fixFunction ($ tokens , $ index );
173+
174+ if (
175+ $ isMultiline
176+ && 'ensure_fully_multiline ' === $ this ->configuration ['on_multiline ' ]
177+ && !$ meaningfulTokenBeforeParenthesis ->isGivenKind (T_LIST )
178+ ) {
179+ $ this ->ensureFunctionFullyMultiline ($ tokens , $ index );
164180 }
165181 }
166182 }
167183
168- /**
169- * {@inheritdoc}
170- */
171184 protected function createConfigurationDefinition (): FixerConfigurationResolverInterface
172185 {
173186 return new FixerConfigurationResolver ([
@@ -185,13 +198,13 @@ protected function createConfigurationDefinition(): FixerConfigurationResolverIn
185198 (new FixerOptionBuilder ('after_heredoc ' , 'Whether the whitespace between heredoc end and comma should be removed. ' ))
186199 ->setAllowedTypes (['bool ' ])
187200 ->setDefault (false )
188- ->setNormalizer ( static function ( Options $ options , $ value ) {
189- if (\ PHP_VERSION_ID < 70300 && $ value ) {
190- throw new InvalidOptionsForEnvException ( ' "after_heredoc" option can only be enabled with PHP 7.3+. ' );
191- }
192-
193- return $ value ;
194- } )
201+ ->getOption (),
202+ ( new FixerOptionBuilder (
203+ ' attribute_placement ' ,
204+ ' Defines how to handle argument attributes when function definition is multiline. '
205+ ))
206+ -> setAllowedValues ([ ' ignore ' , ' same_line ' , ' standalone ' ])
207+ -> setDefault ( ' standalone ' )
195208 ->getOption (),
196209 ]);
197210 }
@@ -255,8 +268,6 @@ private function fixFunction(Tokens $tokens, int $startFunctionIndex): bool
255268 $ this ->fixSpace ($ tokens , $ index );
256269 if (!$ isMultiline && $ this ->isNewline ($ tokens [$ index + 1 ])) {
257270 $ isMultiline = true ;
258-
259- break ;
260271 }
261272 }
262273 }
@@ -298,11 +309,7 @@ private function ensureSingleLine(Tokens $tokens, int $index): bool
298309
299310 $ content = Preg::replace ('/\R\h*/ ' , '' , $ tokens [$ index ]->getContent ());
300311
301- if ('' !== $ content ) {
302- $ tokens [$ index ] = new Token ([T_WHITESPACE , $ content ]);
303- } else {
304- $ tokens ->clearAt ($ index );
305- }
312+ $ tokens ->ensureWhitespaceAtIndex ($ index , 0 , $ content );
306313
307314 return true ;
308315 }
@@ -314,7 +321,7 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction
314321 do {
315322 $ prevWhitespaceTokenIndex = $ tokens ->getPrevTokenOfKind (
316323 $ searchIndex ,
317- [[T_WHITESPACE ]]
324+ [[T_ENCAPSED_AND_WHITESPACE ], [ T_WHITESPACE ]],
318325 );
319326
320327 $ searchIndex = $ prevWhitespaceTokenIndex ;
@@ -324,13 +331,14 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction
324331
325332 if (null === $ prevWhitespaceTokenIndex ) {
326333 $ existingIndentation = '' ;
334+ } elseif (!$ tokens [$ prevWhitespaceTokenIndex ]->isGivenKind (T_WHITESPACE )) {
335+ return ;
327336 } else {
328337 $ existingIndentation = $ tokens [$ prevWhitespaceTokenIndex ]->getContent ();
329338 $ lastLineIndex = strrpos ($ existingIndentation , "\n" );
330339 $ existingIndentation = false === $ lastLineIndex
331340 ? $ existingIndentation
332- : substr ($ existingIndentation , $ lastLineIndex + 1 )
333- ;
341+ : substr ($ existingIndentation , $ lastLineIndex + 1 );
334342 }
335343
336344 $ indentation = $ existingIndentation .$ this ->whitespacesConfig ->getIndent ();
@@ -369,7 +377,23 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction
369377 continue ;
370378 }
371379
372- if ($ token ->equals (', ' ) && !$ tokens [$ tokens ->getNextMeaningfulToken ($ index )]->equals (') ' )) {
380+ if ($ tokens [$ tokens ->getNextMeaningfulToken ($ index )]->equals (') ' )) {
381+ continue ;
382+ }
383+
384+ if ($ token ->isGivenKind (CT ::T_ATTRIBUTE_CLOSE )) {
385+ if ('standalone ' === $ this ->configuration ['attribute_placement ' ]) {
386+ $ this ->fixNewline ($ tokens , $ index , $ indentation );
387+ } elseif ('same_line ' === $ this ->configuration ['attribute_placement ' ]) {
388+ $ this ->ensureSingleLine ($ tokens , $ index + 1 );
389+ $ tokens ->ensureWhitespaceAtIndex ($ index + 1 , 0 , ' ' );
390+ }
391+ $ index = $ tokens ->findBlockStart (Tokens::BLOCK_TYPE_ATTRIBUTE , $ index );
392+
393+ continue ;
394+ }
395+
396+ if ($ token ->equals (', ' )) {
373397 $ this ->fixNewline ($ tokens , $ index , $ indentation );
374398 }
375399 }
@@ -378,7 +402,7 @@ private function ensureFunctionFullyMultiline(Tokens $tokens, int $startFunction
378402 }
379403
380404 /**
381- * Method to insert newline after comma or opening parenthesis.
405+ * Method to insert newline after comma, attribute or opening parenthesis.
382406 *
383407 * @param int $index index of a comma
384408 * @param string $indentation the indentation that should be used
@@ -393,6 +417,10 @@ private function fixNewline(Tokens $tokens, int $index, string $indentation, boo
393417 if ($ tokens [$ index + 2 ]->isComment ()) {
394418 $ nextMeaningfulTokenIndex = $ tokens ->getNextMeaningfulToken ($ index + 2 );
395419 if (!$ this ->isNewline ($ tokens [$ nextMeaningfulTokenIndex - 1 ])) {
420+ if ($ tokens [$ nextMeaningfulTokenIndex - 1 ]->isWhitespace ()) {
421+ $ tokens ->clearAt ($ nextMeaningfulTokenIndex - 1 );
422+ }
423+
396424 $ tokens ->ensureWhitespaceAtIndex ($ nextMeaningfulTokenIndex , 0 , $ this ->whitespacesConfig ->getLineEnding ().$ indentation );
397425 }
398426
@@ -479,10 +507,4 @@ private function isNewline(Token $token): bool
479507 {
480508 return $ token ->isWhitespace () && str_contains ($ token ->getContent (), "\n" );
481509 }
482-
483-
484- public function getName (): string
485- {
486- return 'Nette/ ' . parent ::getName ();
487- }
488510}
0 commit comments