Skip to content

Commit 5428700

Browse files
authored
AbstractFunctionParameterSniff: don't ignore first class callables (#2544)
... but pass them to a dedicated `process_first_class_callable()` method instead to allow sniffs to decide whether to flag these or not. Typical use-case for why first class callables should not be ignored by default: A sniff which ALWAYS flags the use of a certain function, but has different error messages depending on whether parameters are passed or not. In that situation, I believe first class callables should be treated the same as other uses of the target function. First class callables are basically syntax sugar for closures (example: https://3v4l.org/cra8s) and if the sniff would flag the use of the target function within a closure, it is only reasonable to also flag the use of the target function as a first class callable. This commit implements this. The commit does not include tests as there are no sniffs in WPCS for which the above would apply. However, I have manually tested this change via a sniff in an external standard for which this change is relevant.
1 parent 6305020 commit 5428700

File tree

1 file changed

+29
-10
lines changed

1 file changed

+29
-10
lines changed

WordPress/AbstractFunctionParameterSniff.php

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,19 @@ public function process_matched_token( $stackPtr, $group_name, $matched_content
7272
$parameters = PassedParameters::getParameters( $this->phpcsFile, $stackPtr );
7373

7474
if ( empty( $parameters ) ) {
75-
return $this->process_no_parameters( $stackPtr, $group_name, $matched_content );
75+
/*
76+
* Check if this is a first class callable.
77+
*
78+
* No need for extensive defensive coding as the `is_targetted_token()` method has already
79+
* validated the open and close parentheses exist.
80+
*/
81+
$openParens = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
82+
$firstNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $openParens + 1 ), null, true );
83+
if ( \T_ELLIPSIS === $this->tokens[ $firstNonEmpty ]['code'] ) {
84+
return $this->process_first_class_callable( $stackPtr, $group_name, $matched_content );
85+
} else {
86+
return $this->process_no_parameters( $stackPtr, $group_name, $matched_content );
87+
}
7688
} else {
7789
return $this->process_parameters( $stackPtr, $group_name, $matched_content, $parameters );
7890
}
@@ -104,15 +116,6 @@ public function is_targetted_token( $stackPtr ) {
104116
return false;
105117
}
106118

107-
// First class callable.
108-
$firstNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $next + 1 ), null, true );
109-
if ( \T_ELLIPSIS === $this->tokens[ $firstNonEmpty ]['code'] ) {
110-
$secondNonEmpty = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $firstNonEmpty + 1 ), null, true );
111-
if ( \T_CLOSE_PARENTHESIS === $this->tokens[ $secondNonEmpty ]['code'] ) {
112-
return false;
113-
}
114-
}
115-
116119
return true;
117120
}
118121

@@ -147,4 +150,20 @@ abstract public function process_parameters( $stackPtr, $group_name, $matched_co
147150
* normal file processing.
148151
*/
149152
public function process_no_parameters( $stackPtr, $group_name, $matched_content ) {}
153+
154+
/**
155+
* Process the function if it is used as a first class callable.
156+
*
157+
* Defaults to doing nothing. Can be overloaded in child classes to implement specific checks
158+
* on first class callable use of the function.
159+
*
160+
* @param int $stackPtr The position of the current token in the stack.
161+
* @param string $group_name The name of the group which was matched.
162+
* @param string $matched_content The token content (function name) which was matched
163+
* in lowercase.
164+
*
165+
* @return int|void Integer stack pointer to skip forward or void to continue
166+
* normal file processing.
167+
*/
168+
public function process_first_class_callable( $stackPtr, $group_name, $matched_content ) {}
150169
}

0 commit comments

Comments
 (0)