Skip to content

Commit

Permalink
Generate hook names using parsed Node instead of pretty-printed name.
Browse files Browse the repository at this point in the history
Previously, the hook-name printer was reyling on the parser's pretty
printer to generate names, and then post-processing the name to
extract string literal values and concatenations.

Unfortunately this left some cases unhandled, specifically the case
where concatenations are joining function calls with string literals.
Any number of other unexpected situations might arise, and there can
be defects in the redundant code attempting to parse PHP syntax.

In this patch the pretty-printed version of the expression is used
only as a fallback in unrecognized situations. Primarily, a direct
encoding from the parsed syntax tree to string is used to rely on
the parser's own handling of syntax, and making it clearer how to
add additional support for other syntaxes.
  • Loading branch information
dmsnell committed Mar 20, 2024
1 parent 7fc2227 commit e860d73
Showing 1 changed file with 59 additions and 26 deletions.
85 changes: 59 additions & 26 deletions lib/class-hook-reflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,73 @@
class Hook_Reflector extends BaseReflector {

/**
* @return string
*/
public function getName() {
$printer = new PHPParser_PrettyPrinter_Default;
return $this->cleanupName( $printer->prettyPrintExpr( $this->node->args[0]->value ) );
}

/**
* @param string $name
* Hook names are the first argument to actions and filters.
*
* These are expected to be string values or concatenations of strings and variables.
*
* Example:
*
* from: apply_filters( 'option_' . $option_name, $option_value );
* name: option_{$option_name}
*
* from: do_action( 'wp_insert_post', $post_ID, $post, true );
* name: wp_insert_post
*
* from: do_action( "{$old_status}_to_{$new_status}", $post );
* name: {$old_status}_to_{$new_status}
*
* from: do_action( $filter_name, $args );
* name: {$filter_name}
*
* @param ?\PhpParser\Node $node Use this instead of the current node.
* @return string
*/
private function cleanupName( $name ) {
$matches = array();

// quotes on both ends of a string
if ( preg_match( '/^[\'"]([^\'"]*)[\'"]$/', $name, $matches ) ) {
return $matches[1];
}
public function getName( $node = null ) {
/**
* The current Node being examined.
*
* @var \PhpParser\Node
*/
$node = $node ?? $this->node->args[0]->value;
$printer = new PHPParser_PrettyPrinter_Default;
$name = $printer->prettyPrintExpr( $node );

// two concatenated things, last one of them a variable
if ( preg_match(
'/(?:[\'"]([^\'"]*)[\'"]\s*\.\s*)?' . // First filter name string (optional)
'(\$[^\s]*)' . // Dynamic variable
'(?:\s*\.\s*[\'"]([^\'"]*)[\'"])?/', // Second filter name string (optional)
$name, $matches ) ) {
if ( $node instanceof \PhpParser\Node\Scalar\String_ ) {
// "'action'" -> "action"
return $node->value;
} elseif ( $node instanceof \PhpParser\Node\Scalar\Encapsed ) {
// '"action_{$var}"' -> 'action_{$var}'
$name = '';

if ( isset( $matches[3] ) ) {
return $matches[1] . '{' . $matches[2] . '}' . $matches[3];
} else {
return $matches[1] . '{' . $matches[2] . '}';
foreach ( $node->parts as $part ) {
if ( is_string( $part ) ) {
$name .= $part;
} else {
$name .= $this->getName( $part );
}
}

return $name;
} elseif ( $node instanceof \PhpParser\Node\Expr\BinaryOp\Concat ) {
// '"action_" . $var' -> 'action_{$var}'
return $this->getName( $node->left ) . $this->getName( $node->right );
} elseif ( $node instanceof \PhpParser\Node\Expr\PropertyFetch ) {
// '$this->action' -> '{$this->action}'
return "{{$name}}";
} elseif ( $node instanceof \PhpParser\Node\Expr\Variable ) {
// '$action' -> '{$action}'
return "{\${$node->name}}";
}

/*
* If none of these known constructions match, then
* fallback to the pretty-printed version of the node.
*
* For improving the quality of the hook-name generation,
* replace this return statement by throwing an exception
* to determine which cases aren't handled, and then add
* them above.
*/
return $name;
}

Expand Down

0 comments on commit e860d73

Please sign in to comment.