Skip to content

Commit 3449b0b

Browse files
committed
Complete WP_Parser_Node helper methods, add tests
1 parent 62943d6 commit 3449b0b

File tree

3 files changed

+266
-41
lines changed

3 files changed

+266
-41
lines changed

tests/parser/WP_Parser_Node_Tests.php

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-node.php';
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
class WP_Parser_Node_Tests extends TestCase {
8+
public function testEmptyChildren(): void {
9+
$node = new WP_Parser_Node( 1, 'root' );
10+
11+
$this->assertFalse( $node->has_child() );
12+
$this->assertFalse( $node->has_child_node() );
13+
$this->assertFalse( $node->has_child_token() );
14+
15+
$this->assertNull( $node->get_child() );
16+
$this->assertNull( $node->get_child_node() );
17+
$this->assertNull( $node->get_child_node( 'root' ) );
18+
$this->assertNull( $node->get_child_token() );
19+
$this->assertNull( $node->get_child_token( 1 ) );
20+
21+
$this->assertNull( $node->get_descendant_node() );
22+
$this->assertNull( $node->get_descendant_token() );
23+
24+
$this->assertEmpty( $node->get_children() );
25+
$this->assertEmpty( $node->get_child_nodes() );
26+
$this->assertEmpty( $node->get_child_nodes( 'root' ) );
27+
$this->assertEmpty( $node->get_child_tokens() );
28+
$this->assertEmpty( $node->get_child_tokens( 1 ) );
29+
30+
$this->assertEmpty( $node->get_descendants() );
31+
$this->assertEmpty( $node->get_descendant_nodes() );
32+
$this->assertEmpty( $node->get_descendant_nodes( 'root' ) );
33+
$this->assertEmpty( $node->get_descendant_tokens() );
34+
$this->assertEmpty( $node->get_descendant_tokens( 1 ) );
35+
}
36+
37+
public function testNodeTree(): void {
38+
// Prepare nodes and tokens.
39+
$root = new WP_Parser_Node( 1, 'root' );
40+
$n_keyword = new WP_Parser_Node( 2, 'keyword' );
41+
$n_expr_a = new WP_Parser_Node( 3, 'expr' );
42+
$n_expr_b = new WP_Parser_Node( 3, 'expr' );
43+
$n_expr_c = new WP_Parser_Node( 3, 'expr' );
44+
$t_select = new WP_Parser_Token( 100, 'SELECT' );
45+
$t_comma = new WP_Parser_Token( 200, ',' );
46+
$t_plus = new WP_Parser_Token( 300, '+' );
47+
$t_one = new WP_Parser_Token( 400, '1' );
48+
$t_two_a = new WP_Parser_Token( 400, '2' );
49+
$t_two_b = new WP_Parser_Token( 400, '2' );
50+
$t_eof = new WP_Parser_Token( 500, '' );
51+
52+
// Prepare a tree.
53+
//
54+
// A simplified testing tree for an input like "SELECT 1 + 2, 2".
55+
//
56+
// root
57+
// |- keyword
58+
// | |- "SELECT"
59+
// |- expr [a]
60+
// | |- "1"
61+
// | |- "+"
62+
// | |- expr [c]
63+
// | | |- "2" [b]
64+
// |- ","
65+
// |- expr [b]
66+
// | |- "2" [a]
67+
// |- EOF
68+
$root->append_child( $n_keyword );
69+
$root->append_child( $n_expr_a );
70+
$root->append_child( $t_comma );
71+
$root->append_child( $n_expr_b );
72+
$root->append_child( $t_eof );
73+
74+
$n_keyword->append_child( $t_select );
75+
$n_expr_a->append_child( $t_one );
76+
$n_expr_a->append_child( $t_plus );
77+
$n_expr_a->append_child( $n_expr_c );
78+
$n_expr_b->append_child( $t_two_a );
79+
$n_expr_c->append_child( $t_two_b );
80+
81+
// Test "has" methods.
82+
$this->assertTrue( $root->has_child() );
83+
$this->assertTrue( $root->has_child_node() );
84+
$this->assertTrue( $root->has_child_token() );
85+
86+
// Test single child methods.
87+
$this->assertSame( $n_keyword, $root->get_child() );
88+
$this->assertSame( $n_keyword, $root->get_child_node() );
89+
$this->assertSame( $n_keyword, $root->get_child_node( 'keyword' ) );
90+
$this->assertSame( $n_expr_a, $root->get_child_node( 'expr' ) );
91+
$this->assertSame( $t_comma, $root->get_child_token() );
92+
$this->assertSame( $t_comma, $root->get_child_token( 200 ) );
93+
$this->assertNull( $root->get_child_token( 100 ) );
94+
95+
// Test multiple children methods.
96+
$this->assertSame( array( $n_keyword, $n_expr_a, $t_comma, $n_expr_b, $t_eof ), $root->get_children() );
97+
$this->assertSame( array( $n_keyword, $n_expr_a, $n_expr_b ), $root->get_child_nodes() );
98+
$this->assertSame( array( $n_expr_a, $n_expr_b ), $root->get_child_nodes( 'expr' ) );
99+
$this->assertSame( array(), $root->get_child_nodes( 'root' ) );
100+
$this->assertSame( array( $t_comma, $t_eof ), $root->get_child_tokens() );
101+
$this->assertSame( array( $t_comma ), $root->get_child_tokens( 200 ) );
102+
$this->assertSame( array(), $root->get_child_tokens( 100 ) );
103+
104+
// Test single descendant methods.
105+
// @TODO: Consider breadth-first search vs depth-first search.
106+
$this->assertSame( $n_keyword, $root->get_descendant_node() );
107+
$this->assertSame( $n_expr_a, $root->get_descendant_node( 'expr' ) );
108+
$this->assertSame( null, $root->get_descendant_node( 'root' ) );
109+
$this->assertSame( $t_comma, $root->get_descendant_token() );
110+
$this->assertSame( $t_one, $root->get_descendant_token( 400 ) );
111+
$this->assertSame( null, $root->get_descendant_token( 123 ) );
112+
113+
// Test multiple descendant methods.
114+
// @TODO: Consider breadth-first search vs depth-first search.
115+
$this->assertSame(
116+
array( $n_keyword, $n_expr_a, $t_comma, $n_expr_b, $t_eof, $t_select, $t_one, $t_plus, $n_expr_c, $t_two_a, $t_two_b ),
117+
$root->get_descendants()
118+
);
119+
$this->assertSame(
120+
array( $n_keyword, $n_expr_a, $n_expr_b, $n_expr_c ),
121+
$root->get_descendant_nodes()
122+
);
123+
$this->assertSame(
124+
array( $n_expr_a, $n_expr_b, $n_expr_c ),
125+
$root->get_descendant_nodes( 'expr' )
126+
);
127+
$this->assertSame(
128+
array(),
129+
$root->get_descendant_nodes( 'root' )
130+
);
131+
$this->assertSame(
132+
array( $t_comma, $t_eof, $t_select, $t_one, $t_plus, $t_two_a, $t_two_b ),
133+
$root->get_descendant_tokens()
134+
);
135+
$this->assertSame(
136+
array( $t_one, $t_two_a, $t_two_b ),
137+
$root->get_descendant_tokens( 400 )
138+
);
139+
$this->assertSame(
140+
array(),
141+
$root->get_descendant_tokens( 123 )
142+
);
143+
}
144+
}

wp-includes/parser/class-wp-parser-node.php

+121-40
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class WP_Parser_Node {
1515
*/
1616
public $rule_id;
1717
public $rule_name;
18-
public $children = array();
18+
private $children = array();
1919

2020
public function __construct( $rule_id, $rule_name ) {
2121
$this->rule_id = $rule_id;
@@ -102,83 +102,164 @@ public function merge_fragment( $node ) {
102102
$this->children = array_merge( $this->children, $node->children );
103103
}
104104

105-
public function has_child( $rule_name ) {
105+
public function has_child(): bool {
106+
return count( $this->children ) > 0;
107+
}
108+
109+
public function has_child_node( ?string $rule_name = null ): bool {
106110
foreach ( $this->children as $child ) {
107-
if ( ( $child instanceof WP_Parser_Node && $child->rule_name === $rule_name ) ) {
111+
if (
112+
$child instanceof WP_Parser_Node
113+
&& ( null === $rule_name || $child->rule_name === $rule_name )
114+
) {
108115
return true;
109116
}
110117
}
111118
return false;
112119
}
113120

114-
public function has_token( $token_id = null ) {
121+
public function has_child_token( ?int $token_id = null ): bool {
115122
foreach ( $this->children as $child ) {
116-
if ( $child instanceof WP_Parser_Token && (
117-
null === $token_id ||
118-
$child->id === $token_id
119-
) ) {
123+
if (
124+
$child instanceof WP_Parser_Token
125+
&& ( null === $token_id || $child->id === $token_id )
126+
) {
120127
return true;
121128
}
122129
}
123130
return false;
124131
}
125132

126-
public function get_token( $token_id = null ) {
133+
134+
public function get_child() {
135+
return $this->children[0] ?? null;
136+
}
137+
138+
public function get_child_node( ?string $rule_name = null ): ?WP_Parser_Node {
127139
foreach ( $this->children as $child ) {
128-
if ( $child instanceof WP_Parser_Token && (
129-
null === $token_id ||
130-
$child->id === $token_id
131-
) ) {
140+
if (
141+
$child instanceof WP_Parser_Node
142+
&& ( null === $rule_name || $child->rule_name === $rule_name )
143+
) {
132144
return $child;
133145
}
134146
}
135147
return null;
136148
}
137149

138-
public function get_child( $rule_name = null ) {
150+
public function get_child_token( ?int $token_id = null ): ?WP_Parser_Token {
139151
foreach ( $this->children as $child ) {
140-
if ( $child instanceof WP_Parser_Node && (
141-
$child->rule_name === $rule_name ||
142-
null === $rule_name
143-
) ) {
152+
if (
153+
$child instanceof WP_Parser_Token
154+
&& ( null === $token_id || $child->id === $token_id )
155+
) {
144156
return $child;
145157
}
146158
}
159+
return null;
147160
}
148161

149-
public function get_descendant( $rule_name ) {
150-
$parse_trees = array( $this );
151-
while ( count( $parse_trees ) ) {
152-
$parse_tree = array_pop( $parse_trees );
153-
if ( $parse_tree->rule_name === $rule_name ) {
154-
return $parse_tree;
162+
public function get_descendant_node( ?string $rule_name = null ): ?WP_Parser_Node {
163+
$nodes = array( $this );
164+
while ( count( $nodes ) ) {
165+
$node = array_shift( $nodes );
166+
$child = $node->get_child_node( $rule_name );
167+
if ( $child ) {
168+
return $child;
169+
}
170+
$children = $node->get_child_nodes();
171+
if ( count( $children ) > 0 ) {
172+
array_push( $nodes, ...$children );
155173
}
156-
array_push( $parse_trees, ...$parse_tree->get_children() );
157174
}
158175
return null;
159176
}
160177

161-
public function get_descendants( $rule_name ) {
162-
$parse_trees = array( $this );
178+
public function get_descendant_token( ?int $token_id = null ): ?WP_Parser_Token {
179+
$nodes = array( $this );
180+
while ( count( $nodes ) ) {
181+
$node = array_shift( $nodes );
182+
$child = $node->get_child_token( $token_id );
183+
if ( $child ) {
184+
return $child;
185+
}
186+
$children = $node->get_child_nodes();
187+
if ( count( $children ) > 0 ) {
188+
array_push( $nodes, ...$children );
189+
}
190+
}
191+
return null;
192+
}
193+
194+
public function get_children(): array {
195+
return $this->children;
196+
}
197+
198+
public function get_child_nodes( ?string $rule_name = null ): array {
199+
$nodes = array();
200+
foreach ( $this->children as $child ) {
201+
if (
202+
$child instanceof WP_Parser_Node
203+
&& ( null === $rule_name || $child->rule_name === $rule_name )
204+
) {
205+
$nodes[] = $child;
206+
}
207+
}
208+
return $nodes;
209+
}
210+
211+
public function get_child_tokens( ?int $token_id = null ): array {
212+
$tokens = array();
213+
foreach ( $this->children as $child ) {
214+
if (
215+
$child instanceof WP_Parser_Token
216+
&& ( null === $token_id || $child->id === $token_id )
217+
) {
218+
$tokens[] = $child;
219+
}
220+
}
221+
return $tokens;
222+
}
223+
224+
public function get_descendants(): array {
225+
$nodes = array( $this );
226+
$all_descendants = array();
227+
while ( count( $nodes ) ) {
228+
$node = array_shift( $nodes );
229+
$all_descendants = array_merge( $all_descendants, $node->get_children() );
230+
$children = $node->get_child_nodes();
231+
if ( count( $children ) > 0 ) {
232+
array_push( $nodes, ...$children );
233+
}
234+
}
235+
return $all_descendants;
236+
}
237+
238+
public function get_descendant_nodes( ?string $rule_name = null ): array {
239+
$nodes = array( $this );
163240
$all_descendants = array();
164-
while ( count( $parse_trees ) ) {
165-
$parse_tree = array_pop( $parse_trees );
166-
$all_descendants = array_merge( $all_descendants, $parse_tree->get_children( $rule_name ) );
167-
array_push( $parse_trees, ...$parse_tree->get_children() );
241+
while ( count( $nodes ) ) {
242+
$node = array_shift( $nodes );
243+
$all_descendants = array_merge( $all_descendants, $node->get_child_nodes( $rule_name ) );
244+
$children = $node->get_child_nodes();
245+
if ( count( $children ) > 0 ) {
246+
array_push( $nodes, ...$children );
247+
}
168248
}
169249
return $all_descendants;
170250
}
171251

172-
public function get_children( $rule_name = null ) {
173-
$matches = array();
174-
foreach ( $this->children as $child ) {
175-
if ( $child instanceof WP_Parser_Node && (
176-
null === $rule_name ||
177-
$child->rule_name === $rule_name
178-
) ) {
179-
$matches[] = $child;
252+
public function get_descendant_tokens( ?int $token_id = null ): array {
253+
$nodes = array( $this );
254+
$all_descendants = array();
255+
while ( count( $nodes ) ) {
256+
$node = array_shift( $nodes );
257+
$all_descendants = array_merge( $all_descendants, $node->get_child_tokens( $token_id ) );
258+
$children = $node->get_child_nodes();
259+
if ( count( $children ) > 0 ) {
260+
array_push( $nodes, ...$children );
180261
}
181262
}
182-
return $matches;
263+
return $all_descendants;
183264
}
184265
}

wp-includes/parser/class-wp-parser.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private function parse_recursive( $rule_id ) {
115115
return false;
116116
}
117117

118-
if ( 0 === count( $node->children ) ) {
118+
if ( ! $node->has_child() ) {
119119
return true;
120120
}
121121

0 commit comments

Comments
 (0)