Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 113 additions & 1 deletion lib/Completion/Bridge/TolerantParser/CompletionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@
use Microsoft\PhpParser\Node\ArrayElement;
use Microsoft\PhpParser\Node\Attribute;
use Microsoft\PhpParser\Node\AttributeGroup;
use Microsoft\PhpParser\Node\CaseStatementNode;
use Microsoft\PhpParser\Node\CatchClause;
use Microsoft\PhpParser\Node\ClassBaseClause;
use Microsoft\PhpParser\Node\ClassInterfaceClause;
use Microsoft\PhpParser\Node\ClassMembersNode;
use Microsoft\PhpParser\Node\ConstElement;
use Microsoft\PhpParser\Node\DelimitedList\ExpressionList;
use Microsoft\PhpParser\Node\DelimitedList\MatchArmConditionList;
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
use Microsoft\PhpParser\Node\Expression;
use Microsoft\PhpParser\Node\Expression\AnonymousFunctionCreationExpression;
use Microsoft\PhpParser\Node\Expression\ArgumentExpression;
use Microsoft\PhpParser\Node\Expression\CallExpression;
use Microsoft\PhpParser\Node\Expression\MemberAccessExpression;
use Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression;
use Microsoft\PhpParser\Node\Expression\BinaryExpression;
use Microsoft\PhpParser\Node\Expression\Variable;
use Microsoft\PhpParser\Node\InterfaceBaseClause;
Expand All @@ -29,13 +35,20 @@
use Microsoft\PhpParser\Node\StatementNode;
use Microsoft\PhpParser\Node\Statement\ClassDeclaration;
use Microsoft\PhpParser\Node\Statement\CompoundStatementNode;
use Microsoft\PhpParser\Node\Statement\DoStatement;
use Microsoft\PhpParser\Node\Statement\EchoStatement;
use Microsoft\PhpParser\Node\Statement\EnumDeclaration;
use Microsoft\PhpParser\Node\Statement\ForStatement;
use Microsoft\PhpParser\Node\Statement\ForeachStatement;
use Microsoft\PhpParser\Node\Statement\ExpressionStatement;
use Microsoft\PhpParser\Node\Statement\InlineHtml;
use Microsoft\PhpParser\Node\Statement\IfStatementNode;
use Microsoft\PhpParser\Node\Statement\WhileStatement;
use Microsoft\PhpParser\Node\Statement\InterfaceDeclaration;
use Microsoft\PhpParser\Node\Statement\NamespaceUseDeclaration;
use Microsoft\PhpParser\Node\Statement\SwitchStatementNode;
use Microsoft\PhpParser\Node\Statement\TraitDeclaration;
use Microsoft\PhpParser\Node\StringLiteral;
use Microsoft\PhpParser\Node\TraitUseClause;
use Microsoft\PhpParser\TokenKind;
use Phpactor\TextDocument\ByteOffset;
Expand All @@ -62,7 +75,20 @@ public static function expression(?Node $node): bool
return false;
}

if ($parent instanceof ArgumentExpression) {
if (
$node instanceof Variable
|| $node instanceof ExpressionStatement
|| $node instanceof MemberAccessExpression
|| $node instanceof ScopedPropertyAccessExpression
|| $node instanceof StringLiteral
) {
return false;
}

if (
$node instanceof CallExpression
|| $parent instanceof ArgumentExpression
) {
return true;
}

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

public static function statement(Node $node, ByteOffset $offset): bool
{
if ($node instanceof NamespaceUseDeclaration) {
return false;
}

if ($node instanceof CaseStatementNode) {
return true;
}

if ($node instanceof CompoundStatementNode) {
if ($node->parent instanceof MethodDeclaration && $node->openBrace instanceof MissingToken) {
return false;
}

$lastStmt = \end($node->statements);
if (false === $lastStmt || $lastStmt->getEndPosition() > $offset->toInt()) {
return true;
}

return !$lastStmt instanceof EchoStatement;
}

if ($node instanceof Expression) {
return false;
}

if ($node instanceof SwitchStatementNode) {
if ([] === $node->caseStatements) {
return false;
}

return $offset->toInt() > $node->caseStatements[0]->getStartPosition();
}

if (
$node->parent && $node->parent->getEndPosition() === $offset->toInt()
&& (
$node->parent instanceof WhileStatement
|| $node->parent instanceof DoStatement
|| $node->parent instanceof IfStatementNode
|| $node->parent instanceof CatchClause
|| $node->parent instanceof ForeachStatement
|| $node->parent instanceof SwitchStatementNode
) && $node->parent->openParen instanceof MissingToken
) {
return true;
}

if (
$node instanceof WhileStatement
|| $node instanceof IfStatementNode
|| $node instanceof DoStatement
|| $node instanceof CatchClause
|| $node instanceof ForStatement
|| $node instanceof ForeachStatement
|| $node instanceof EchoStatement
|| $node->parent instanceof ExpressionList
|| $node->parent instanceof WhileStatement
|| $node->parent instanceof DoStatement
|| $node->parent instanceof IfStatementNode
|| $node->parent instanceof CatchClause
|| $node->parent instanceof ForeachStatement
|| $node->parent instanceof SwitchStatementNode
) {
return false;
}

return $node->parent instanceof CaseStatementNode
|| $node->parent instanceof SourceFileNode
|| $node->parent instanceof CompoundStatementNode
|| $node->parent?->parent instanceof CaseStatementNode
|| $node->parent?->parent instanceof CompoundStatementNode;
}

public static function loopOrSwitch(Node $node): bool
{
return $node->getFirstAncestor(
DoStatement::class,
ForStatement::class,
ForeachStatement::class,
SwitchStatementNode::class,
WhileStatement::class,
) instanceof Node;
}

public static function declaration(Node $node, ByteOffset $offset): bool
{
if (!$node->parent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Generator;
use Microsoft\PhpParser\Node;
use Microsoft\PhpParser\Node\MethodDeclaration;
use Microsoft\PhpParser\Node\StatementNode;
use Phpactor\Completion\Bridge\TolerantParser\CompletionContext;
use Phpactor\Completion\Bridge\TolerantParser\TolerantCompletor;
use Phpactor\Completion\Core\Suggestion;
Expand All @@ -15,6 +16,10 @@

class KeywordCompletor implements TolerantCompletor
{
private const EXPRESSIONS = [
'match' => " (\$1) {\$0\n}",
'throw' => ' $1',
];
private const MAGIC_METHODS = [
'__construct' => "(\$1)\n{\$0\n}",
'__call' => "(string \\\$\${1:name}, array \\\$\${2:arguments}): \${3:mixed}\n{\$0\n}",
Expand All @@ -34,11 +39,26 @@ class KeywordCompletor implements TolerantCompletor
'__unset' => "(string \\\$\${1:name}): void\n{\$0\n}",
'__wakeup' => "(): void\n{\$0\n}",
];
private const STATEMENTS = [
'break' => '$1;$0',
'continue' => '$1;$0',
'do' => " {\n\t\$0\n} while (\$2);",
'echo' => ' $1;$0',
'for' => " (\${1:expr1}, \${2:expr2}, \${3:expr3}) {\n\t\$0\n}",
'foreach' => " (\\\$\${1:expr} as \\\$\${2:key} => \\\$\${3:value}) {\$0\n}",
'if' => " (\$1) {\$0\n}",
'return' => ' $1;$0',
'switch' => " (\\\$\${1:expr}) {\n\tcase \${2:expr}:\n\t\t\$0\n}",
'throw' => ' $1;$0',
'try' => " {\$3\n} catch (\${1:Exception} \\\$\${2:error}) {\$4\n}",
'while' => " (\$1) {\$0\n}",
'yield' => ' $1;$0',
];

public function complete(Node $node, TextDocument $source, ByteOffset $offset): Generator
{
if (CompletionContext::promotedPropertyVisibility($node)) {
yield from $this->keywords(['private ', 'public ', 'protected ', ]);
yield from $this->keywords(['private ', 'public ', 'protected ']);
return true;
}
if (CompletionContext::classClause($node, $offset)) {
Expand All @@ -64,7 +84,21 @@ public function complete(Node $node, TextDocument $source, ByteOffset $offset):
return true;
}

if (!$node instanceof MethodDeclaration && CompletionContext::classMembersBody($node->parent)) {
if (CompletionContext::statement($node, $offset)) {
yield from $this->statements(CompletionContext::loopOrSwitch($node));
return true;
}

if (CompletionContext::expression($node)) {
yield from $this->expressions();
return true;
}

if (
!$node instanceof MethodDeclaration
&& CompletionContext::classMembersBody($node->parent)
&& !$node->parent instanceof StatementNode
) {
yield from $this->keywords([
'function ',
'const ',
Expand All @@ -80,6 +114,20 @@ public function complete(Node $node, TextDocument $source, ByteOffset $offset):
return true;
}

/**
* @return Generator<Suggestion>
*/
private function expressions(): Generator
{
foreach (self::EXPRESSIONS as $name => $snippet) {
yield Suggestion::createWithOptions($name . ' ', [
'type' => Suggestion::TYPE_KEYWORD,
'priority' => -255,
'snippet' => $name . $snippet,
]);
}
}

/**
* @return Generator<Suggestion>
*/
Expand All @@ -97,6 +145,24 @@ private function methods(): Generator
}
}

/**
* @return Generator<Suggestion>
*/
private function statements(bool $loop): Generator
{
foreach (self::STATEMENTS as $name => $snippet) {
if (!$loop && in_array($name, ['continue', 'break'], true)) {
continue;
}

yield Suggestion::createWithOptions($name . ' ', [
'type' => Suggestion::TYPE_KEYWORD,
'priority' => -255,
'snippet' => $name . $snippet,
]);
}
}

/**
* @return Generator<Suggestion>
* @param string[] $keywords
Expand All @@ -106,7 +172,7 @@ private function keywords(array $keywords): Generator
foreach ($keywords as $keyword) {
yield Suggestion::createWithOptions($keyword, [
'type' => Suggestion::TYPE_KEYWORD,
'priority' => 1,
'priority' => -255,
]);
}
}
Expand Down
Loading
Loading