Skip to content

Commit afd07c5

Browse files
committed
Fix more edge cases
1 parent e99debf commit afd07c5

File tree

3 files changed

+257
-1
lines changed

3 files changed

+257
-1
lines changed

src/ASTConverter/ASTConverter.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,27 @@ private static function _phpparser_node_to_ast_node($n) {
210210
], $startLine);
211211
case 'PhpParser\Node\Expr\List_':
212212
return self::_phpparser_list_to_ast_list($n, $startLine);
213+
case 'PhpParser\Node\Expr\MethodCall':
214+
return self::_ast_node_method_call(
215+
self::_phpparser_node_to_ast_node($n->var),
216+
is_string($n->name) ? $n->name : self::_phpparser_node_to_ast_node($n->name),
217+
self::_phpparser_arg_list_to_ast_arg_list($n->args, $startLine),
218+
$startLine
219+
);
213220
case 'PhpParser\Node\Expr\New_':
214221
return astnode(\ast\AST_NEW, 0, [
215222
'class' => self::_phpparser_node_to_ast_node($n->class),
216223
'args' => self::_phpparser_arg_list_to_ast_arg_list($n->args, $startLine),
217224
], $startLine);
218225
case 'PhpParser\Node\Expr\PropertyFetch':
219226
return self::_phpparser_propertyfetch_to_ast_prop($n, $startLine);
227+
case 'PhpParser\Node\Expr\StaticCall':
228+
return self::_ast_node_static_call(
229+
self::_phpparser_node_to_ast_node($n->class),
230+
is_string($n->name) ? $n->name : self::_phpparser_node_to_ast_node($n->name),
231+
self::_phpparser_arg_list_to_ast_arg_list($n->args, $startLine),
232+
$startLine
233+
);
220234
case 'PhpParser\Node\Expr\UnaryMinus':
221235
return self::_ast_node_unary_op(\ast\flags\UNARY_MINUS, self::_phpparser_node_to_ast_node($n->expr), $startLine);
222236
case 'PhpParser\Node\Expr\UnaryPlus':
@@ -571,7 +585,8 @@ private static function _ast_node_param(bool $byRef, $variadic, $type, $name, $d
571585
private static function _ast_node_nullable_type(\ast\Node $type, int $line) {
572586
$node = new \ast\Node;
573587
$node->kind = \ast\AST_NULLABLE_TYPE;
574-
$node->flags = 0;
588+
// FIXME: Why is this a special case in php-ast? (e.g. nullable int has no flags on the nullable node)
589+
$node->flags = ($type->kind === \ast\AST_TYPE && $type->flags === \ast\flags\TYPE_ARRAY) ? $type->flags : 0;
575590
$node->lineno = $line;
576591
$node->children = ['type' => $type];
577592
return $node;
@@ -1002,6 +1017,21 @@ private static function _ast_node_call($expr, $args, int $startLine) : \ast\Node
10021017
return astnode(\ast\AST_CALL, 0, ['expr' => $expr, 'args' => $args], $startLine);
10031018
}
10041019

1020+
private static function _ast_node_method_call($expr, $method, \ast\Node $args, int $startLine) : \ast\Node {
1021+
return astnode(\ast\AST_METHOD_CALL, 0, ['expr' => $expr, 'method' => $method, 'args' => $args], $startLine);
1022+
}
1023+
1024+
private static function _ast_node_static_call($class, $method, \ast\Node $args, int $startLine) : \ast\Node {
1025+
// TODO: is this applicable?
1026+
if (is_string($class)) {
1027+
if (substr($class, 0, 1) === '\\') {
1028+
$expr = substr($class, 1);
1029+
}
1030+
$class = astnode(\ast\AST_NAME, \ast\flags\NAME_FQ, ['name' => $class], $startLine);
1031+
}
1032+
return astnode(\ast\AST_STATIC_CALL, 0, ['class' => $class, 'method' => $method, 'args' => $args], $startLine);
1033+
}
1034+
10051035
private static function _extract_phpdoc_comment($comments) : ?string {
10061036
if (is_string($comments)) {
10071037
return $comments;

test_files/src/methods.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
Foo::bar("k");
3+
$A->baz(2);
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<?php declare(strict_types = 1);
2+
namespace ASTConverter\Tests;
3+
use ASTConverter\ASTConverter;
4+
5+
require_once __DIR__ . '/../../src/util.php';
6+
7+
class ErrorTolerantConversionTest extends \PHPUnit\Framework\TestCase {
8+
9+
public function setUp() {
10+
parent::setUp();
11+
ASTConverter::set_should_add_placeholders(false);
12+
}
13+
14+
public function testIncompleteVar() {
15+
ASTConverter::set_should_add_placeholders(false);
16+
$incompleteContents = <<<'EOT'
17+
<?php
18+
function foo() {
19+
$a = $
20+
}
21+
EOT;
22+
$validContents = <<<'EOT'
23+
<?php
24+
function foo() {
25+
26+
}
27+
EOT;
28+
$this->_testFallbackFromParser($incompleteContents, $validContents);
29+
}
30+
31+
public function testIncompleteVarWithPlaceholder() {
32+
ASTConverter::set_should_add_placeholders(true);
33+
$incompleteContents = <<<'EOT'
34+
<?php
35+
function foo() {
36+
$a = $
37+
}
38+
EOT;
39+
$validContents = <<<'EOT'
40+
<?php
41+
function foo() {
42+
$a = $__INCOMPLETE_VARIABLE__;
43+
}
44+
EOT;
45+
$this->_testFallbackFromParser($incompleteContents, $validContents);
46+
}
47+
48+
public function testIncompleteProperty() {
49+
ASTConverter::set_should_add_placeholders(false);
50+
$incompleteContents = <<<'EOT'
51+
<?php
52+
function foo() {
53+
$c;
54+
$a = $b->
55+
}
56+
EOT;
57+
$validContents = <<<'EOT'
58+
<?php
59+
function foo() {
60+
$c;
61+
62+
}
63+
EOT;
64+
$this->_testFallbackFromParser($incompleteContents, $validContents);
65+
}
66+
67+
public function testIncompletePropertyWithPlaceholder() {
68+
ASTConverter::set_should_add_placeholders(true);
69+
$incompleteContents = <<<'EOT'
70+
<?php
71+
function foo() {
72+
$c;
73+
$a = $b->
74+
}
75+
EOT;
76+
$validContents = <<<'EOT'
77+
<?php
78+
function foo() {
79+
$c;
80+
$a = $b->__INCOMPLETE_PROPERTY__;
81+
}
82+
EOT;
83+
$this->_testFallbackFromParser($incompleteContents, $validContents);
84+
}
85+
86+
public function testIncompleteMethod() {
87+
ASTConverter::set_should_add_placeholders(false);
88+
$incompleteContents = <<<'EOT'
89+
<?php
90+
function foo() {
91+
$b;
92+
$a = Bar::
93+
}
94+
EOT;
95+
$validContents = <<<'EOT'
96+
<?php
97+
function foo() {
98+
$b;
99+
100+
}
101+
EOT;
102+
$this->_testFallbackFromParser($incompleteContents, $validContents);
103+
}
104+
105+
public function testIncompleteMethodWithPlaceholder() {
106+
ASTConverter::set_should_add_placeholders(true);
107+
$incompleteContents = <<<'EOT'
108+
<?php
109+
function foo() {
110+
$b;
111+
$a = Bar::
112+
}
113+
EOT;
114+
$validContents = <<<'EOT'
115+
<?php
116+
function foo() {
117+
$b;
118+
$a = Bar::__INCOMPLETE_CLASS_CONST__;
119+
}
120+
EOT;
121+
$this->_testFallbackFromParser($incompleteContents, $validContents);
122+
}
123+
124+
public function testMiscNoise() {
125+
ASTConverter::set_should_add_placeholders(false);
126+
$incompleteContents = <<<'EOT'
127+
<?php
128+
function foo() {
129+
$b;
130+
|
131+
}
132+
EOT;
133+
$validContents = <<<'EOT'
134+
<?php
135+
function foo() {
136+
$b;
137+
138+
}
139+
EOT;
140+
$this->_testFallbackFromParser($incompleteContents, $validContents);
141+
}
142+
143+
public function testMiscNoiseWithPlaceholders() {
144+
ASTConverter::set_should_add_placeholders(true);
145+
$incompleteContents = <<<'EOT'
146+
<?php
147+
function foo() {
148+
$b;
149+
|
150+
}
151+
EOT;
152+
$validContents = <<<'EOT'
153+
<?php
154+
function foo() {
155+
$b;
156+
157+
}
158+
EOT;
159+
$this->_testFallbackFromParser($incompleteContents, $validContents);
160+
}
161+
162+
public function testIncompleteArithmeticWithPlaceholders() {
163+
ASTConverter::set_should_add_placeholders(true);
164+
$incompleteContents = <<<'EOT'
165+
<?php
166+
function foo() {
167+
($b * $c) +
168+
}
169+
EOT;
170+
$validContents = <<<'EOT'
171+
<?php
172+
function foo() {
173+
$b * $c;
174+
}
175+
EOT;
176+
$this->_testFallbackFromParser($incompleteContents, $validContents);
177+
}
178+
179+
private function _testFallbackFromParser(string $incompleteContents, string $validContents) {
180+
$ast = \ast\parse_code($validContents, ASTConverter::AST_VERSION);
181+
$this->assertInstanceOf('\ast\Node', $ast, 'Examples(for validContents) must be syntactically valid PHP parseable by php-ast');
182+
$errors = [];
183+
$phpParserNode = ASTConverter::phpparser_parse($incompleteContents, true, $errors);
184+
$fallback_ast = ASTConverter::phpparser_to_phpast($phpParserNode, ASTConverter::AST_VERSION);
185+
$this->assertInstanceOf('\ast\Node', $fallback_ast, 'The fallback must also return a tree of php-ast nodes');
186+
$fallbackASTRepr = var_export($fallback_ast, true);
187+
$originalASTRepr = var_export($ast, true);
188+
189+
if ($fallbackASTRepr !== $originalASTRepr) {
190+
$dump = 'could not dump';
191+
$nodeDumper = new \PhpParser\NodeDumper([
192+
'dumpComments' => true,
193+
'dumpPositions' => true,
194+
]);
195+
try {
196+
$dump = $nodeDumper->dump($phpParserNode);
197+
} catch (\PhpParser\Error $e) {
198+
}
199+
$original_ast_dump = \ast_dump($ast);
200+
$parser_export = var_export($phpParserNode, true);
201+
$this->assertSame($originalASTRepr, $fallbackASTRepr, <<<EOT
202+
The fallback must return the same tree of php-ast nodes
203+
Code:
204+
$incompleteContents
205+
206+
Closest Valid Code:
207+
$validContents
208+
209+
Original AST:
210+
$original_ast_dump
211+
212+
PHP-Parser(simplified):
213+
$dump
214+
EOT
215+
216+
/*
217+
PHP-Parser(unsimplified):
218+
$parser_export
219+
*/
220+
);
221+
}
222+
}
223+
}

0 commit comments

Comments
 (0)