@@ -94,6 +94,11 @@ class Generic_Sniffs_CodeAnalysis_VariableAnalysisSniff implements PHP_CodeSniff
9494 */
9595 private $ _scopes = array ();
9696
97+ /**
98+ * A regexp for matching variable names in double-quoted strings.
99+ */
100+ private $ _double_quoted_variable_regexp = '|(?<! \\\\)(?: \\\\{2})*\${?([a-zA-Z0-9_]+)}?| ' ;
101+
97102 /**
98103 * Array of known pass-by-reference functions and the argument(s) which are passed
99104 * by reference, the arguments are numbered starting from 1 and an elipsis '...'
@@ -367,6 +372,7 @@ public function register() {
367372 T_DOUBLE_QUOTED_STRING ,
368373 T_HEREDOC ,
369374 T_CLOSE_CURLY_BRACKET ,
375+ T_STRING ,
370376 );
371377 }//end register()
372378
@@ -398,6 +404,9 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) {
398404 ($ token ['code ' ] === T_HEREDOC )) {
399405 return $ this ->processVariableInString ($ phpcsFile , $ stackPtr );
400406 }
407+ if (($ token ['code ' ] === T_STRING ) && ($ token ['content ' ] === 'compact ' )) {
408+ return $ this ->processCompact ($ phpcsFile , $ stackPtr );
409+ }
401410 if (($ token ['code ' ] === T_CLOSE_CURLY_BRACKET ) &&
402411 isset ($ token ['scope_condition ' ])) {
403412 return $ this ->processScopeClose ($ phpcsFile , $ token ['scope_condition ' ]);
@@ -669,7 +678,9 @@ function findFunctionCallArguments(
669678 ) {
670679 $ tokens = $ phpcsFile ->getTokens ();
671680
672- if ($ tokens [$ stackPtr ]['code ' ] !== T_STRING ) {
681+ // Slight hack: also allow this to find args for array constructor.
682+ // TODO: probably should refactor into three functions: arg-finding and bracket-finding
683+ if (($ tokens [$ stackPtr ]['code ' ] !== T_STRING ) && ($ tokens [$ stackPtr ]['code ' ] !== T_ARRAY )) {
673684 // Assume $stackPtr is something within the brackets, find our function call
674685 if (($ stackPtr = $ this ->findFunctionCall ($ phpcsFile , $ stackPtr )) === false ) {
675686 return false ;
@@ -1318,8 +1329,7 @@ protected function processVariableInString(
13181329 $ tokens = $ phpcsFile ->getTokens ();
13191330 $ token = $ tokens [$ stackPtr ];
13201331
1321- $ pattern = '|(?<! \\\\)(?: \\\\{2})*\${?([a-zA-Z0-9_]+)}?| ' ;
1322- if (!preg_match_all ($ pattern , $ token ['content ' ], $ matches )) {
1332+ if (!preg_match_all ($ this ->_double_quoted_variable_regexp , $ token ['content ' ], $ matches )) {
13231333 return ;
13241334 }
13251335
@@ -1337,6 +1347,85 @@ protected function processVariableInString(
13371347 }
13381348 }
13391349
1350+ protected function processCompactArguments (
1351+ PHP_CodeSniffer_File
1352+ $ phpcsFile ,
1353+ $ stackPtr ,
1354+ $ arguments ,
1355+ $ currScope
1356+ ) {
1357+ $ tokens = $ phpcsFile ->getTokens ();
1358+ $ token = $ tokens [$ stackPtr ];
1359+
1360+ foreach ($ arguments as $ argumentPtrs ) {
1361+ $ argumentPtrs = array_values (array_filter ($ argumentPtrs ,
1362+ function ($ argumentPtr ) use ($ tokens ) {
1363+ return $ tokens [$ argumentPtr ]['code ' ] !== T_WHITESPACE ;
1364+ }));
1365+ if (empty ($ argumentPtrs )) {
1366+ continue ;
1367+ }
1368+ if (!isset ($ tokens [$ argumentPtrs [0 ]])) {
1369+ continue ;
1370+ }
1371+ $ argument_first_token = $ tokens [$ argumentPtrs [0 ]];
1372+ if ($ argument_first_token ['code ' ] === T_ARRAY ) {
1373+ // It's an array argument, recurse.
1374+ if (($ array_arguments = $ this ->findFunctionCallArguments ($ phpcsFile , $ argumentPtrs [0 ])) !== false ) {
1375+ $ this ->processCompactArguments ($ phpcsFile , $ stackPtr , $ array_arguments , $ currScope );
1376+ }
1377+ continue ;
1378+ }
1379+ if (count ($ argumentPtrs ) > 1 ) {
1380+ // Complex argument, we can't handle it, ignore.
1381+ continue ;
1382+ }
1383+ if ($ argument_first_token ['code ' ] === T_CONSTANT_ENCAPSED_STRING ) {
1384+ // Single-quoted string literal, ie compact('whatever').
1385+ // Substr is to strip the enclosing single-quotes.
1386+ $ varName = substr ($ argument_first_token ['content ' ], 1 , -1 );
1387+ $ this ->markVariableReadAndWarnIfUndefined ($ phpcsFile , $ varName , $ argumentPtrs [0 ], $ currScope );
1388+ continue ;
1389+ }
1390+ if ($ argument_first_token ['code ' ] === T_DOUBLE_QUOTED_STRING ) {
1391+ // Double-quoted string literal.
1392+ if (preg_match ($ this ->_double_quoted_variable_regexp , $ argument_first_token ['content ' ])) {
1393+ // Bail if the string needs variable expansion, that's runtime stuff.
1394+ continue ;
1395+ }
1396+ // Substr is to strip the enclosing double-quotes.
1397+ $ varName = substr ($ argument_first_token ['content ' ], 1 , -1 );
1398+ $ this ->markVariableReadAndWarnIfUndefined ($ phpcsFile , $ varName , $ argumentPtrs [0 ], $ currScope );
1399+ continue ;
1400+ }
1401+ }
1402+ }
1403+
1404+ /**
1405+ * Called to process variables named in a call to compact().
1406+ *
1407+ * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this
1408+ * token was found.
1409+ * @param int $stackPtr The position where the call to compact()
1410+ * was found.
1411+ *
1412+ * @return void
1413+ */
1414+ protected function processCompact (
1415+ PHP_CodeSniffer_File
1416+ $ phpcsFile ,
1417+ $ stackPtr
1418+ ) {
1419+ $ tokens = $ phpcsFile ->getTokens ();
1420+ $ token = $ tokens [$ stackPtr ];
1421+
1422+ $ currScope = $ this ->findVariableScope ($ phpcsFile , $ stackPtr );
1423+
1424+ if (($ arguments = $ this ->findFunctionCallArguments ($ phpcsFile , $ stackPtr )) !== false ) {
1425+ $ this ->processCompactArguments ($ phpcsFile , $ stackPtr , $ arguments , $ currScope );
1426+ }
1427+ }
1428+
13401429 /**
13411430 * Called to process the end of a scope.
13421431 *
0 commit comments