@@ -126,7 +126,7 @@ public function register()
126126 * @param int $stackPtr The position of the current token
127127 * in the stack passed in $tokens.
128128 *
129- * @return void| int
129+ * @return int
130130 */
131131 public function process (File $ phpcsFile , $ stackPtr )
132132 {
@@ -146,13 +146,14 @@ public function process(File $phpcsFile, $stackPtr)
146146 }
147147 }
148148
149- $ lastOpenTag = $ stackPtr ;
150- $ lastCloseTag = null ;
151- $ openScopes = [];
152- $ adjustments = [];
153- $ setIndents = [];
154- $ disableExactEnd = 0 ;
155- $ tokenIndent = 0 ;
149+ $ lastOpenTag = $ stackPtr ;
150+ $ lastCloseTag = null ;
151+ $ openScopes = [];
152+ $ adjustments = [];
153+ $ setIndents = [];
154+ $ disableExactStack = [];
155+ $ disableExactEnd = 0 ;
156+ $ tokenIndent = 0 ;
156157
157158 $ tokens = $ phpcsFile ->getTokens ();
158159 $ first = $ phpcsFile ->findFirstOnLine (T_INLINE_HTML , $ stackPtr );
@@ -232,6 +233,7 @@ public function process(File $phpcsFile, $stackPtr)
232233 if ($ tokens [$ i ]['code ' ] === T_OPEN_PARENTHESIS
233234 && isset ($ tokens [$ i ]['parenthesis_closer ' ]) === true
234235 ) {
236+ $ disableExactStack [$ tokens [$ i ]['parenthesis_closer ' ]] = $ tokens [$ i ]['parenthesis_closer ' ];
235237 $ disableExactEnd = max ($ disableExactEnd , $ tokens [$ i ]['parenthesis_closer ' ]);
236238 if ($ this ->debug === true ) {
237239 $ line = $ tokens [$ i ]['line ' ];
@@ -337,7 +339,14 @@ public function process(File $phpcsFile, $stackPtr)
337339 echo "\t* open tag is inside condition; using open tag * " .PHP_EOL ;
338340 }
339341
340- $ checkIndent = ($ tokens [$ lastOpenTag ]['column ' ] - 1 );
342+ $ first = $ phpcsFile ->findFirstOnLine ([T_WHITESPACE , T_INLINE_HTML ], $ lastOpenTag , true );
343+ if ($ this ->debug === true ) {
344+ $ line = $ tokens [$ first ]['line ' ];
345+ $ type = $ tokens [$ first ]['type ' ];
346+ echo "\t* first token on line $ line is $ first ( $ type) * " .PHP_EOL ;
347+ }
348+
349+ $ checkIndent = ($ tokens [$ first ]['column ' ] - 1 );
341350 if (isset ($ adjustments [$ condition ]) === true ) {
342351 $ checkIndent += $ adjustments [$ condition ];
343352 }
@@ -483,12 +492,7 @@ public function process(File $phpcsFile, $stackPtr)
483492
484493 $ arrayOpener = $ tokens [$ arrayCloser ]['bracket_opener ' ];
485494 if ($ tokens [$ arrayCloser ]['line ' ] !== $ tokens [$ arrayOpener ]['line ' ]) {
486- $ first = $ phpcsFile ->findFirstOnLine (T_WHITESPACE , $ arrayOpener , true );
487- $ checkIndent = ($ tokens [$ first ]['column ' ] - 1 );
488- if (isset ($ adjustments [$ first ]) === true ) {
489- $ checkIndent += $ adjustments [$ first ];
490- }
491-
495+ $ first = $ phpcsFile ->findFirstOnLine (T_WHITESPACE , $ arrayOpener , true );
492496 $ exact = false ;
493497
494498 if ($ this ->debug === true ) {
@@ -524,6 +528,11 @@ public function process(File $phpcsFile, $stackPtr)
524528 $ first = $ phpcsFile ->findNext (T_WHITESPACE , ($ first + 1 ), null , true );
525529 }
526530
531+ $ checkIndent = ($ tokens [$ first ]['column ' ] - 1 );
532+ if (isset ($ adjustments [$ first ]) === true ) {
533+ $ checkIndent += $ adjustments [$ first ];
534+ }
535+
527536 if (isset ($ tokens [$ first ]['scope_closer ' ]) === true
528537 && $ tokens [$ first ]['scope_closer ' ] === $ first
529538 ) {
@@ -609,11 +618,11 @@ public function process(File $phpcsFile, $stackPtr)
609618
610619 // Scope closers reset the required indent to the same level as the opening condition.
611620 if (($ checkToken !== null
612- && isset ($ openScopes [$ checkToken ]) === true
621+ && ( isset ($ openScopes [$ checkToken ]) === true
613622 || (isset ($ tokens [$ checkToken ]['scope_condition ' ]) === true
614623 && isset ($ tokens [$ checkToken ]['scope_closer ' ]) === true
615624 && $ tokens [$ checkToken ]['scope_closer ' ] === $ checkToken
616- && $ tokens [$ checkToken ]['line ' ] !== $ tokens [$ tokens [$ checkToken ]['scope_opener ' ]]['line ' ]))
625+ && $ tokens [$ checkToken ]['line ' ] !== $ tokens [$ tokens [$ checkToken ]['scope_opener ' ]]['line ' ])))
617626 || ($ checkToken === null
618627 && isset ($ openScopes [$ i ]) === true )
619628 ) {
@@ -796,6 +805,19 @@ public function process(File $phpcsFile, $stackPtr)
796805 ) {
797806 $ exact = true ;
798807
808+ if ($ disableExactEnd > $ checkToken ) {
809+ foreach ($ disableExactStack as $ disableExactStackEnd ) {
810+ if ($ disableExactStackEnd < $ checkToken ) {
811+ continue ;
812+ }
813+
814+ if ($ tokens [$ checkToken ]['conditions ' ] === $ tokens [$ disableExactStackEnd ]['conditions ' ]) {
815+ $ exact = false ;
816+ break ;
817+ }
818+ }
819+ }
820+
799821 $ lastOpener = null ;
800822 if (empty ($ openScopes ) === false ) {
801823 end ($ openScopes );
@@ -844,16 +866,34 @@ public function process(File $phpcsFile, $stackPtr)
844866 && $ tokens [($ checkToken + 1 )]['code ' ] !== T_DOUBLE_COLON
845867 ) {
846868 $ next = $ phpcsFile ->findNext (Tokens::$ emptyTokens , ($ checkToken + 1 ), null , true );
847- if ($ next === false || $ tokens [$ next ]['code ' ] !== T_CLOSURE ) {
848- if ($ this ->debug === true ) {
849- $ line = $ tokens [$ checkToken ]['line ' ];
850- $ type = $ tokens [$ checkToken ]['type ' ];
851- echo "\t* method prefix ( $ type) found on line $ line; indent set to exact * " .PHP_EOL ;
869+ if ($ next === false
870+ || ($ tokens [$ next ]['code ' ] !== T_CLOSURE
871+ && $ tokens [$ next ]['code ' ] !== T_VARIABLE
872+ && $ tokens [$ next ]['code ' ] !== T_FN )
873+ ) {
874+ $ isMethodPrefix = true ;
875+ if (isset ($ tokens [$ checkToken ]['nested_parenthesis ' ]) === true ) {
876+ $ parenthesis = array_keys ($ tokens [$ checkToken ]['nested_parenthesis ' ]);
877+ $ deepestOpen = array_pop ($ parenthesis );
878+ if (isset ($ tokens [$ deepestOpen ]['parenthesis_owner ' ]) === true
879+ && $ tokens [$ tokens [$ deepestOpen ]['parenthesis_owner ' ]]['code ' ] === T_FUNCTION
880+ ) {
881+ // This is constructor property promotion and not a method prefix.
882+ $ isMethodPrefix = false ;
883+ }
852884 }
853885
854- $ exact = true ;
855- }
856- }
886+ if ($ isMethodPrefix === true ) {
887+ if ($ this ->debug === true ) {
888+ $ line = $ tokens [$ checkToken ]['line ' ];
889+ $ type = $ tokens [$ checkToken ]['type ' ];
890+ echo "\t* method prefix ( $ type) found on line $ line; indent set to exact * " .PHP_EOL ;
891+ }
892+
893+ $ exact = true ;
894+ }
895+ }//end if
896+ }//end if
857897
858898 // JS property indentation has to be exact or else if will break
859899 // things like function and object indentation.
@@ -890,8 +930,6 @@ public function process(File $phpcsFile, $stackPtr)
890930 }
891931 }
892932 }
893-
894- $ checkIndent = (int ) (ceil ($ checkIndent / $ this ->indent ) * $ this ->indent );
895933 }//end if
896934
897935 // Close tags needs to be indented to exact column positions.
@@ -911,7 +949,8 @@ public function process(File $phpcsFile, $stackPtr)
911949 // Don't perform strict checking on chained method calls since they
912950 // are often covered by custom rules.
913951 if ($ checkToken !== null
914- && $ tokens [$ checkToken ]['code ' ] === T_OBJECT_OPERATOR
952+ && ($ tokens [$ checkToken ]['code ' ] === T_OBJECT_OPERATOR
953+ || $ tokens [$ checkToken ]['code ' ] === T_NULLSAFE_OBJECT_OPERATOR )
915954 && $ exact === true
916955 ) {
917956 $ exact = false ;
@@ -988,18 +1027,38 @@ public function process(File $phpcsFile, $stackPtr)
9881027 }
9891028
9901029 if ($ this ->tabIndent === true ) {
991- $ error .= '%s tabs, found %s ' ;
992- $ data = [
993- floor ($ checkIndent / $ this ->tabWidth ),
994- floor ($ tokenIndent / $ this ->tabWidth ),
995- ];
1030+ $ expectedTabs = floor ($ checkIndent / $ this ->tabWidth );
1031+ $ foundTabs = floor ($ tokenIndent / $ this ->tabWidth );
1032+ $ foundSpaces = ($ tokenIndent - ($ foundTabs * $ this ->tabWidth ));
1033+ if ($ foundSpaces > 0 ) {
1034+ if ($ foundTabs > 0 ) {
1035+ $ error .= '%s tabs, found %s tabs and %s spaces ' ;
1036+ $ data = [
1037+ $ expectedTabs ,
1038+ $ foundTabs ,
1039+ $ foundSpaces ,
1040+ ];
1041+ } else {
1042+ $ error .= '%s tabs, found %s spaces ' ;
1043+ $ data = [
1044+ $ expectedTabs ,
1045+ $ foundSpaces ,
1046+ ];
1047+ }
1048+ } else {
1049+ $ error .= '%s tabs, found %s ' ;
1050+ $ data = [
1051+ $ expectedTabs ,
1052+ $ foundTabs ,
1053+ ];
1054+ }//end if
9961055 } else {
9971056 $ error .= '%s spaces, found %s ' ;
9981057 $ data = [
9991058 $ checkIndent ,
10001059 $ tokenIndent ,
10011060 ];
1002- }
1061+ }//end if
10031062
10041063 if ($ this ->debug === true ) {
10051064 $ line = $ tokens [$ checkToken ]['line ' ];
@@ -1030,15 +1089,17 @@ public function process(File $phpcsFile, $stackPtr)
10301089
10311090 // Don't check indents exactly between arrays as they tend to have custom rules.
10321091 if ($ tokens [$ i ]['code ' ] === T_OPEN_SHORT_ARRAY ) {
1092+ $ disableExactStack [$ tokens [$ i ]['bracket_closer ' ]] = $ tokens [$ i ]['bracket_closer ' ];
10331093 $ disableExactEnd = max ($ disableExactEnd , $ tokens [$ i ]['bracket_closer ' ]);
10341094 if ($ this ->debug === true ) {
1035- $ line = $ tokens [$ i ]['line ' ];
1036- $ type = $ tokens [$ disableExactEnd ]['type ' ];
1095+ $ line = $ tokens [$ i ]['line ' ];
1096+ $ type = $ tokens [$ disableExactEnd ]['type ' ];
1097+ $ endLine = $ tokens [$ disableExactEnd ]['line ' ];
10371098 echo "Opening short array bracket found on line $ line " .PHP_EOL ;
10381099 if ($ disableExactEnd === $ tokens [$ i ]['bracket_closer ' ]) {
1039- echo "\t=> disabling exact indent checking until $ disableExactEnd ( $ type) " .PHP_EOL ;
1100+ echo "\t=> disabling exact indent checking until $ disableExactEnd ( $ type) on line $ endLine " .PHP_EOL ;
10401101 } else {
1041- echo "\t=> continuing to disable exact indent checking until $ disableExactEnd ( $ type) " .PHP_EOL ;
1102+ echo "\t=> continuing to disable exact indent checking until $ disableExactEnd ( $ type) on line $ endLine " .PHP_EOL ;
10421103 }
10431104 }
10441105 }
@@ -1050,7 +1111,6 @@ public function process(File $phpcsFile, $stackPtr)
10501111 ) {
10511112 if ($ this ->debug === true ) {
10521113 $ line = $ tokens [$ i ]['line ' ];
1053- $ type = $ tokens [$ disableExactEnd ]['type ' ];
10541114 echo "Here/nowdoc found on line $ line " .PHP_EOL ;
10551115 }
10561116
@@ -1074,8 +1134,11 @@ public function process(File $phpcsFile, $stackPtr)
10741134 if ($ tokens [$ i ]['code ' ] === T_CONSTANT_ENCAPSED_STRING
10751135 || $ tokens [$ i ]['code ' ] === T_DOUBLE_QUOTED_STRING
10761136 ) {
1077- $ i = $ phpcsFile ->findNext ($ tokens [$ i ]['code ' ], ($ i + 1 ), null , true );
1078- $ i --;
1137+ $ nextNonTextString = $ phpcsFile ->findNext ($ tokens [$ i ]['code ' ], ($ i + 1 ), null , true );
1138+ if ($ nextNonTextString !== false ) {
1139+ $ i = ($ nextNonTextString - 1 );
1140+ }
1141+
10791142 continue ;
10801143 }
10811144
@@ -1162,7 +1225,7 @@ public function process(File $phpcsFile, $stackPtr)
11621225 if ($ this ->debug === true ) {
11631226 $ type = str_replace ('_ ' , ' ' , strtolower (substr ($ tokens [$ i ]['type ' ], 2 )));
11641227 $ line = $ tokens [$ i ]['line ' ];
1165- echo "* ignoring single-line $ type on line $ line " .PHP_EOL ;
1228+ echo "* ignoring single-line $ type on line $ line * " .PHP_EOL ;
11661229 }
11671230
11681231 $ i = $ closer ;
@@ -1232,14 +1295,24 @@ public function process(File $phpcsFile, $stackPtr)
12321295 if ($ this ->debug === true ) {
12331296 $ line = $ tokens [$ i ]['line ' ];
12341297 $ type = $ tokens [$ i ]['type ' ];
1235- echo "* ignoring single-line $ type on line $ line " .PHP_EOL ;
1298+ echo "* ignoring single-line $ type on line $ line * " .PHP_EOL ;
12361299 }
12371300
12381301 $ i = $ closer ;
12391302 continue ;
12401303 }
12411304
12421305 $ condition = $ tokens [$ tokens [$ i ]['scope_condition ' ]]['code ' ];
1306+ if ($ condition === T_FN ) {
1307+ if ($ this ->debug === true ) {
1308+ $ line = $ tokens [$ tokens [$ i ]['scope_condition ' ]]['line ' ];
1309+ echo "* ignoring arrow function on line $ line * " .PHP_EOL ;
1310+ }
1311+
1312+ $ i = $ closer ;
1313+ continue ;
1314+ }
1315+
12431316 if (isset (Tokens::$ scopeOpeners [$ condition ]) === true
12441317 && in_array ($ condition , $ this ->nonIndentingScopes , true ) === false
12451318 ) {
@@ -1279,7 +1352,7 @@ public function process(File $phpcsFile, $stackPtr)
12791352 if ($ tokens [$ i ]['line ' ] === $ tokens [$ closer ]['line ' ]) {
12801353 if ($ this ->debug === true ) {
12811354 $ line = $ tokens [$ i ]['line ' ];
1282- echo "* ignoring single-line JS object on line $ line " .PHP_EOL ;
1355+ echo "* ignoring single-line JS object on line $ line * " .PHP_EOL ;
12831356 }
12841357
12851358 $ i = $ closer ;
@@ -1309,11 +1382,14 @@ public function process(File $phpcsFile, $stackPtr)
13091382 continue ;
13101383 }//end if
13111384
1312- // Closing an anon class or function.
1385+ // Closing an anon class, closure, or match.
1386+ // Each may be returned, which can confuse control structures that
1387+ // use return as a closer, like CASE statements.
13131388 if (isset ($ tokens [$ i ]['scope_condition ' ]) === true
13141389 && $ tokens [$ i ]['scope_closer ' ] === $ i
13151390 && ($ tokens [$ tokens [$ i ]['scope_condition ' ]]['code ' ] === T_CLOSURE
1316- || $ tokens [$ tokens [$ i ]['scope_condition ' ]]['code ' ] === T_ANON_CLASS )
1391+ || $ tokens [$ tokens [$ i ]['scope_condition ' ]]['code ' ] === T_ANON_CLASS
1392+ || $ tokens [$ tokens [$ i ]['scope_condition ' ]]['code ' ] === T_MATCH )
13171393 ) {
13181394 if ($ this ->debug === true ) {
13191395 $ type = str_replace ('_ ' , ' ' , strtolower (substr ($ tokens [$ tokens [$ i ]['scope_condition ' ]]['type ' ], 2 )));
0 commit comments