Skip to content

Commit 80ded80

Browse files
committed
Generate hook names using parsed Node instead of pretty-printed name.
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.
1 parent 7fc2227 commit 80ded80

File tree

1 file changed

+57
-26
lines changed

1 file changed

+57
-26
lines changed

lib/class-hook-reflector.php

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace WP_Parser;
44

55
use phpDocumentor\Reflection\BaseReflector;
6+
use PhpParser\Node;
67
use PHPParser_PrettyPrinter_Default;
78

89
/**
@@ -11,40 +12,70 @@
1112
class Hook_Reflector extends BaseReflector {
1213

1314
/**
14-
* @return string
15-
*/
16-
public function getName() {
17-
$printer = new PHPParser_PrettyPrinter_Default;
18-
return $this->cleanupName( $printer->prettyPrintExpr( $this->node->args[0]->value ) );
19-
}
20-
21-
/**
22-
* @param string $name
15+
* Hook names are the first argument to actions and filters.
2316
*
24-
* @return string
17+
* These are expected to be string values or concatenations of strings and variables.
18+
*
19+
* Example:
20+
*
21+
* from: apply_filters( 'option_' . $option_name, $option_value );
22+
* name: option_{$option_name}
23+
*
24+
* from: do_action( 'wp_insert_post', $post_ID, $post, true );
25+
* name: wp_insert_post
26+
*
27+
* from: do_action( "{$old_status}_to_{$new_status}", $post );
28+
* name: {$old_status}_to_{$new_status}
29+
*
30+
* from: do_action( $filter_name, $args );
31+
* name: {$filter_name}
32+
*
33+
* @param ?Node $node Which node to examine; defaults to the parser's current node.
34+
* @return string Represents the hook's name, including any interpolations into the hook name.
2535
*/
26-
private function cleanupName( $name ) {
27-
$matches = array();
28-
29-
// quotes on both ends of a string
30-
if ( preg_match( '/^[\'"]([^\'"]*)[\'"]$/', $name, $matches ) ) {
31-
return $matches[1];
36+
public function getName( $node = null ) {
37+
if ( null === $node ) {
38+
$node = $this->node->args[0]->value;
3239
}
40+
$printer = new PHPParser_PrettyPrinter_Default;
41+
$name = $printer->prettyPrintExpr( $node );
3342

34-
// two concatenated things, last one of them a variable
35-
if ( preg_match(
36-
'/(?:[\'"]([^\'"]*)[\'"]\s*\.\s*)?' . // First filter name string (optional)
37-
'(\$[^\s]*)' . // Dynamic variable
38-
'(?:\s*\.\s*[\'"]([^\'"]*)[\'"])?/', // Second filter name string (optional)
39-
$name, $matches ) ) {
43+
if ( $node instanceof \PhpParser\Node\Scalar\String_ ) {
44+
// "'action'" -> "action"
45+
return $node->value;
46+
} elseif ( $node instanceof \PhpParser\Node\Scalar\Encapsed ) {
47+
// '"action_{$var}"' -> 'action_{$var}'
48+
$name = '';
4049

41-
if ( isset( $matches[3] ) ) {
42-
return $matches[1] . '{' . $matches[2] . '}' . $matches[3];
43-
} else {
44-
return $matches[1] . '{' . $matches[2] . '}';
50+
foreach ( $node->parts as $part ) {
51+
if ( is_string( $part ) ) {
52+
$name .= $part;
53+
} else {
54+
$name .= $this->getName( $part );
55+
}
4556
}
57+
58+
return $name;
59+
} elseif ( $node instanceof \PhpParser\Node\Expr\BinaryOp\Concat ) {
60+
// '"action_" . $var' -> 'action_{$var}'
61+
return $this->getName( $node->left ) . $this->getName( $node->right );
62+
} elseif ( $node instanceof \PhpParser\Node\Expr\PropertyFetch ) {
63+
// '$this->action' -> '{$this->action}'
64+
return "{{$name}}";
65+
} elseif ( $node instanceof \PhpParser\Node\Expr\Variable ) {
66+
// '$action' -> '{$action}'
67+
return "{\${$node->name}}";
4668
}
4769

70+
/*
71+
* If none of these known constructions match, then
72+
* fallback to the pretty-printed version of the node.
73+
*
74+
* For improving the quality of the hook-name generation,
75+
* replace this return statement by throwing an exception
76+
* to determine which cases aren't handled, and then add
77+
* them above.
78+
*/
4879
return $name;
4980
}
5081

0 commit comments

Comments
 (0)