Skip to content

Commit 5f1cea0

Browse files
committed
KeywordCompletor: complete statement keywords
1 parent c1969b9 commit 5f1cea0

4 files changed

Lines changed: 574 additions & 27 deletions

File tree

‎lib/Completion/Bridge/TolerantParser/CompletionContext.php‎

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,21 @@
88
use Microsoft\PhpParser\Node\ArrayElement;
99
use Microsoft\PhpParser\Node\Attribute;
1010
use Microsoft\PhpParser\Node\AttributeGroup;
11+
use Microsoft\PhpParser\Node\CaseStatementNode;
12+
use Microsoft\PhpParser\Node\CatchClause;
1113
use Microsoft\PhpParser\Node\ClassBaseClause;
1214
use Microsoft\PhpParser\Node\ClassInterfaceClause;
1315
use Microsoft\PhpParser\Node\ClassMembersNode;
1416
use Microsoft\PhpParser\Node\ConstElement;
17+
use Microsoft\PhpParser\Node\DelimitedList\ExpressionList;
1518
use Microsoft\PhpParser\Node\DelimitedList\MatchArmConditionList;
1619
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
1720
use Microsoft\PhpParser\Node\Expression;
1821
use Microsoft\PhpParser\Node\Expression\AnonymousFunctionCreationExpression;
1922
use Microsoft\PhpParser\Node\Expression\ArgumentExpression;
23+
use Microsoft\PhpParser\Node\Expression\CallExpression;
24+
use Microsoft\PhpParser\Node\Expression\MemberAccessExpression;
25+
use Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression;
2026
use Microsoft\PhpParser\Node\Expression\BinaryExpression;
2127
use Microsoft\PhpParser\Node\Expression\Variable;
2228
use Microsoft\PhpParser\Node\InterfaceBaseClause;
@@ -29,13 +35,20 @@
2935
use Microsoft\PhpParser\Node\StatementNode;
3036
use Microsoft\PhpParser\Node\Statement\ClassDeclaration;
3137
use Microsoft\PhpParser\Node\Statement\CompoundStatementNode;
38+
use Microsoft\PhpParser\Node\Statement\DoStatement;
39+
use Microsoft\PhpParser\Node\Statement\EchoStatement;
3240
use Microsoft\PhpParser\Node\Statement\EnumDeclaration;
41+
use Microsoft\PhpParser\Node\Statement\ForStatement;
42+
use Microsoft\PhpParser\Node\Statement\ForeachStatement;
3343
use Microsoft\PhpParser\Node\Statement\ExpressionStatement;
3444
use Microsoft\PhpParser\Node\Statement\InlineHtml;
3545
use Microsoft\PhpParser\Node\Statement\IfStatementNode;
3646
use Microsoft\PhpParser\Node\Statement\WhileStatement;
3747
use Microsoft\PhpParser\Node\Statement\InterfaceDeclaration;
48+
use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration;
49+
use Microsoft\PhpParser\Node\Statement\SwitchStatementNode;
3850
use Microsoft\PhpParser\Node\Statement\TraitDeclaration;
51+
use Microsoft\PhpParser\Node\StringLiteral;
3952
use Microsoft\PhpParser\Node\TraitUseClause;
4053
use Microsoft\PhpParser\TokenKind;
4154
use Phpactor\TextDocument\ByteOffset;
@@ -62,7 +75,20 @@ public static function expression(?Node $node): bool
6275
return false;
6376
}
6477

65-
if ($parent instanceof ArgumentExpression) {
78+
if (
79+
$node instanceof Variable
80+
|| $node instanceof ExpressionStatement
81+
|| $node instanceof MemberAccessExpression
82+
|| $node instanceof ScopedPropertyAccessExpression
83+
|| $node instanceof StringLiteral
84+
) {
85+
return false;
86+
}
87+
88+
if (
89+
$node instanceof CallExpression
90+
|| $parent instanceof ArgumentExpression
91+
) {
6692
return true;
6793
}
6894

@@ -306,6 +332,92 @@ public static function methodName(Node $node): bool
306332
return $node->parent->openParen instanceof MissingToken;
307333
}
308334

335+
public static function statement(Node $node, ByteOffset $offset): bool
336+
{
337+
if ($node instanceof NamespaceUseDeclaration) {
338+
return false;
339+
}
340+
341+
if ($node instanceof CaseStatementNode) {
342+
return true;
343+
}
344+
345+
if ($node instanceof CompoundStatementNode) {
346+
if ($node->parent instanceof MethodDeclaration && $node->openBrace instanceof MissingToken) {
347+
return false;
348+
}
349+
350+
$lastStmt = \end($node->statements);
351+
if (false === $lastStmt || $lastStmt->getEndPosition() > $offset->toInt()) {
352+
return true;
353+
}
354+
355+
return !$lastStmt instanceof EchoStatement;
356+
}
357+
358+
if ($node instanceof Expression) {
359+
return false;
360+
}
361+
362+
if ($node instanceof SwitchStatementNode) {
363+
if ([] === $node->caseStatements) {
364+
return false;
365+
}
366+
367+
return $offset->toInt() > $node->caseStatements[0]->getStartPosition();
368+
}
369+
370+
if (
371+
$node->parent && $node->parent->getEndPosition() === $offset->toInt()
372+
&& (
373+
$node->parent instanceof WhileStatement
374+
|| $node->parent instanceof DoStatement
375+
|| $node->parent instanceof IfStatementNode
376+
|| $node->parent instanceof CatchClause
377+
|| $node->parent instanceof ForeachStatement
378+
|| $node->parent instanceof SwitchStatementNode
379+
) && $node->parent->openParen instanceof MissingToken
380+
) {
381+
return true;
382+
}
383+
384+
if (
385+
$node instanceof WhileStatement
386+
|| $node instanceof IfStatementNode
387+
|| $node instanceof DoStatement
388+
|| $node instanceof CatchClause
389+
|| $node instanceof ForStatement
390+
|| $node instanceof ForeachStatement
391+
|| $node instanceof EchoStatement
392+
|| $node->parent instanceof ExpressionList
393+
|| $node->parent instanceof WhileStatement
394+
|| $node->parent instanceof DoStatement
395+
|| $node->parent instanceof IfStatementNode
396+
|| $node->parent instanceof CatchClause
397+
|| $node->parent instanceof ForeachStatement
398+
|| $node->parent instanceof SwitchStatementNode
399+
) {
400+
return false;
401+
}
402+
403+
return $node->parent instanceof CaseStatementNode
404+
|| $node->parent instanceof SourceFileNode
405+
|| $node->parent instanceof CompoundStatementNode
406+
|| $node->parent?->parent instanceof CaseStatementNode
407+
|| $node->parent?->parent instanceof CompoundStatementNode;
408+
}
409+
410+
public static function loopOrSwitch(Node $node): bool
411+
{
412+
return $node->getFirstAncestor(
413+
DoStatement::class,
414+
ForStatement::class,
415+
ForeachStatement::class,
416+
SwitchStatementNode::class,
417+
WhileStatement::class,
418+
) instanceof Node;
419+
}
420+
309421
public static function declaration(Node $node, ByteOffset $offset): bool
310422
{
311423
if (!$node->parent) {

‎lib/Completion/Bridge/TolerantParser/WorseReflection/KeywordCompletor.php‎

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Generator;
88
use Microsoft\PhpParser\Node;
99
use Microsoft\PhpParser\Node\MethodDeclaration;
10+
use Microsoft\PhpParser\Node\StatementNode;
1011
use Phpactor\Completion\Bridge\TolerantParser\CompletionContext;
1112
use Phpactor\Completion\Bridge\TolerantParser\TolerantCompletor;
1213
use Phpactor\Completion\Core\Suggestion;
@@ -15,6 +16,10 @@
1516

1617
class KeywordCompletor implements TolerantCompletor
1718
{
19+
private const EXPRESSIONS = [
20+
'match' => " (\$1) {\$0\n}",
21+
'throw' => ' $1',
22+
];
1823
private const MAGIC_METHODS = [
1924
'__construct' => "(\$1)\n{\$0\n}",
2025
'__call' => "(string \\\$\${1:name}, array \\\$\${2:arguments}): \${3:mixed}\n{\$0\n}",
@@ -34,11 +39,26 @@ class KeywordCompletor implements TolerantCompletor
3439
'__unset' => "(string \\\$\${1:name}): void\n{\$0\n}",
3540
'__wakeup' => "(): void\n{\$0\n}",
3641
];
42+
private const STATEMENTS = [
43+
'break' => '$1;$0',
44+
'continue' => '$1;$0',
45+
'do' => " {\n\t\$0\n} while (\$2);",
46+
'echo' => ' $1;$0',
47+
'for' => " (\${1:expr1}, \${2:expr2}, \${3:expr3}) {\n\t\$0\n}",
48+
'foreach' => " (\\\$\${1:expr} as \\\$\${2:key} => \\\$\${3:value}) {\$0\n}",
49+
'if' => " (\$1) {\$0\n}",
50+
'return' => ' $1;$0',
51+
'switch' => " (\\\$\${1:expr}) {\n\tcase \${2:expr}:\n\t\t\$0\n}",
52+
'throw' => ' $1;$0',
53+
'try' => " {\$3\n} catch (\${1:Exception} \\\$\${2:error}) {\$4\n}",
54+
'while' => " (\$1) {\$0\n}",
55+
'yield' => ' $1;$0',
56+
];
3757

3858
public function complete(Node $node, TextDocument $source, ByteOffset $offset): Generator
3959
{
4060
if (CompletionContext::promotedPropertyVisibility($node)) {
41-
yield from $this->keywords(['private ', 'public ', 'protected ', ]);
61+
yield from $this->keywords(['private ', 'public ', 'protected ']);
4262
return true;
4363
}
4464
if (CompletionContext::classClause($node, $offset)) {
@@ -64,7 +84,21 @@ public function complete(Node $node, TextDocument $source, ByteOffset $offset):
6484
return true;
6585
}
6686

67-
if (!$node instanceof MethodDeclaration && CompletionContext::classMembersBody($node->parent)) {
87+
if (CompletionContext::statement($node, $offset)) {
88+
yield from $this->statements(CompletionContext::loopOrSwitch($node));
89+
return true;
90+
}
91+
92+
if (CompletionContext::expression($node)) {
93+
yield from $this->expressions();
94+
return true;
95+
}
96+
97+
if (
98+
!$node instanceof MethodDeclaration
99+
&& CompletionContext::classMembersBody($node->parent)
100+
&& !$node->parent instanceof StatementNode
101+
) {
68102
yield from $this->keywords([
69103
'function ',
70104
'const ',
@@ -80,6 +114,20 @@ public function complete(Node $node, TextDocument $source, ByteOffset $offset):
80114
return true;
81115
}
82116

117+
/**
118+
* @return Generator<Suggestion>
119+
*/
120+
private function expressions(): Generator
121+
{
122+
foreach (self::EXPRESSIONS as $name => $snippet) {
123+
yield Suggestion::createWithOptions($name . ' ', [
124+
'type' => Suggestion::TYPE_KEYWORD,
125+
'priority' => -255,
126+
'snippet' => $name . $snippet,
127+
]);
128+
}
129+
}
130+
83131
/**
84132
* @return Generator<Suggestion>
85133
*/
@@ -97,6 +145,24 @@ private function methods(): Generator
97145
}
98146
}
99147

148+
/**
149+
* @return Generator<Suggestion>
150+
*/
151+
private function statements(bool $loop): Generator
152+
{
153+
foreach (self::STATEMENTS as $name => $snippet) {
154+
if (!$loop && in_array($name, ['continue', 'break'], true)) {
155+
continue;
156+
}
157+
158+
yield Suggestion::createWithOptions($name . ' ', [
159+
'type' => Suggestion::TYPE_KEYWORD,
160+
'priority' => -255,
161+
'snippet' => $name . $snippet,
162+
]);
163+
}
164+
}
165+
100166
/**
101167
* @return Generator<Suggestion>
102168
* @param string[] $keywords
@@ -106,7 +172,7 @@ private function keywords(array $keywords): Generator
106172
foreach ($keywords as $keyword) {
107173
yield Suggestion::createWithOptions($keyword, [
108174
'type' => Suggestion::TYPE_KEYWORD,
109-
'priority' => 1,
175+
'priority' => -255,
110176
]);
111177
}
112178
}

0 commit comments

Comments
 (0)