From 63da051e72554d41a123b7cb667601ab1f696dd1 Mon Sep 17 00:00:00 2001 From: Phil Nelson Date: Sat, 23 Dec 2017 13:02:37 +1100 Subject: [PATCH 1/7] fix(DefinitionResolver): fix methods with self return type (#550) --- src/DefinitionResolver.php | 8 +++++- .../WithReturnTypehints.php.expected.json | 2 +- .../cases/staticMethodReturnType.php | 2 ++ .../staticMethodReturnType.php.expected.json | 25 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index f36588d..905f9d6 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -729,7 +729,7 @@ class DefinitionResolver foreach ($classDef->getAncestorDefinitions($this->index, true) as $fqn => $def) { $def = $this->index->getDefinition($fqn . $add); if ($def !== null) { - if ($def->type instanceof Types\This) { + if ($def->type instanceof Types\This || $def->type instanceof Types\Self_) { return new Types\Object_(new Fqsen('\\' . $classFqn)); } return $def->type; @@ -1090,6 +1090,12 @@ class DefinitionResolver if ($node->returnType instanceof PhpParser\Token) { // Resolve a string like "bool" to a type object return $this->typeResolver->resolve($node->returnType->getText($node->getFileContents())); + } elseif ($node->returnType->getResolvedName() === 'self') { + $classNode = $node->getFirstAncestor(Node\Statement\ClassDeclaration::class); + if ($classNode) { + $classFqn = (string)$classNode->getNamespacedName(); + return new Types\Object_(new Fqsen('\\' . $classFqn)); + } } return new Types\Object_(new Fqsen('\\' . (string)$node->returnType->getResolvedName())); } diff --git a/tests/Validation/cases/WithReturnTypehints.php.expected.json b/tests/Validation/cases/WithReturnTypehints.php.expected.json index a037d6c..2eb8218 100644 --- a/tests/Validation/cases/WithReturnTypehints.php.expected.json +++ b/tests/Validation/cases/WithReturnTypehints.php.expected.json @@ -71,7 +71,7 @@ }, "containerName": "Fixtures\\Prophecy\\WithReturnTypehints" }, - "type__tostring": "\\self", + "type__tostring": "\\Fixtures\\Prophecy\\WithReturnTypehints", "type": {}, "declarationLine": "public function getSelf(): self {", "documentation": null, diff --git a/tests/Validation/cases/staticMethodReturnType.php b/tests/Validation/cases/staticMethodReturnType.php index 325738a..0ce2654 100644 --- a/tests/Validation/cases/staticMethodReturnType.php +++ b/tests/Validation/cases/staticMethodReturnType.php @@ -5,5 +5,7 @@ class FooClass { return new FooClass(); } + public static function staticSelf(): self { } + public function bar() { } } diff --git a/tests/Validation/cases/staticMethodReturnType.php.expected.json b/tests/Validation/cases/staticMethodReturnType.php.expected.json index 8de5673..041042a 100644 --- a/tests/Validation/cases/staticMethodReturnType.php.expected.json +++ b/tests/Validation/cases/staticMethodReturnType.php.expected.json @@ -50,6 +50,31 @@ "parameters": [] } }, + "FooClass::staticSelf()": { + "fqn": "FooClass::staticSelf()", + "extends": [], + "isMember": true, + "roamed": false, + "isStatic": true, + "canBeInstantiated": false, + "symbolInformation": { + "name": "staticSelf", + "kind": 6, + "location": { + "uri": "./staticMethodReturnType.php" + }, + "containerName": "FooClass" + }, + "type__tostring": "\\FooClass", + "type": {}, + "declarationLine": "public static function staticSelf(): self { }", + "documentation": null, + "signatureInformation": { + "label": "()", + "documentation": null, + "parameters": [] + } + }, "FooClass->bar()": { "fqn": "FooClass->bar()", "extends": [], From a0caf8d18f85cffba816ced7ae1318d22ff63515 Mon Sep 17 00:00:00 2001 From: Jannik Vieten Date: Sat, 23 Dec 2017 03:03:24 +0100 Subject: [PATCH 2/7] docs(used-by): mention Atom's ide-php in README (#559) adds Atom's ide-php package to "used by" section in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d3a4d46..5d95d61 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ Example: - [Eclipse Che](https://eclipse.org/che/) - [Eclipse IDE (LSP4E-PHP)](https://github.com/eclipselabs/lsp4e-php) - NeoVim: [LanguageServer-php-neovim](https://github.com/roxma/LanguageServer-php-neovim) with [LanguageClient neovim](https://github.com/autozimu/LanguageClient-neovim) + - Atom: [ide-php](https://github.com/atom/ide-php) ## Contributing From 425b2390b5b7fc9a54c5913ed7a30ebf6e681ff3 Mon Sep 17 00:00:00 2001 From: Phil Nelson Date: Sun, 24 Dec 2017 20:52:49 +1100 Subject: [PATCH 3/7] fix(DefinitionResolver): fix crash on unknown foreach type (#562) Fix when unknown type is found in foreach expression --- fixtures/completion/foreach.php | 3 +++ src/DefinitionResolver.php | 1 + tests/Server/TextDocument/CompletionTest.php | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/fixtures/completion/foreach.php b/fixtures/completion/foreach.php index df8b6df..b6fa5be 100644 --- a/fixtures/completion/foreach.php +++ b/fixtures/completion/foreach.php @@ -35,3 +35,6 @@ foreach ($array3 as $key => $value) { foreach ($bar->test() as $value) { $ } + +foreach ($unknownArray as $unknown) { + $unkno diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 905f9d6..a408dd4 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -1122,6 +1122,7 @@ class DefinitionResolver if ($collectionType instanceof Types\Array_) { return $collectionType->getValueType(); } + return new Types\Mixed_(); } // PROPERTIES, CONSTS, CLASS CONSTS, ASSIGNMENT EXPRESSIONS diff --git a/tests/Server/TextDocument/CompletionTest.php b/tests/Server/TextDocument/CompletionTest.php index ded5941..80d3026 100644 --- a/tests/Server/TextDocument/CompletionTest.php +++ b/tests/Server/TextDocument/CompletionTest.php @@ -691,6 +691,21 @@ class CompletionTest extends TestCase ), ] ], + 'foreach unknown type' => [ + new Position(39, 10), + [ + new CompletionItem( + '$unknown', + CompletionItemKind::VARIABLE, + 'mixed', + null, + null, + null, + null, + new TextEdit(new Range(new Position(39, 10), new Position(39, 10)), 'wn') + ), + ] + ], ]; } From 1cfba8b6bb5d65f49fc059097bb347d1c31189b1 Mon Sep 17 00:00:00 2001 From: Phil Nelson Date: Mon, 25 Dec 2017 12:55:48 +1100 Subject: [PATCH 4/7] fix(DefinitionResolver): don't crash if foreach key isn't a variable (#564) --- fixtures/completion/foreach.php | 2 +- src/DefinitionResolver.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fixtures/completion/foreach.php b/fixtures/completion/foreach.php index b6fa5be..4278f00 100644 --- a/fixtures/completion/foreach.php +++ b/fixtures/completion/foreach.php @@ -36,5 +36,5 @@ foreach ($bar->test() as $value) { $ } -foreach ($unknownArray as $unknown) { +foreach ($unknownArray as $member->access => $unknown) { $unkno diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index a408dd4..72bdce4 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -571,7 +571,9 @@ class DefinitionResolver // If we get to a ForeachStatement, check the keys and values if ($n instanceof Node\Statement\ForeachStatement) { - if ($n->foreachKey && $n->foreachKey->expression->getName() === $name) { + if ($n->foreachKey instanceof Node\Expression\Variable + && $n->foreachKey->expression->getName() === $name + ) { return $n->foreachKey; } if ($n->foreachValue From 8439da999a09f3f05988d0210e6018000df434da Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Thu, 28 Dec 2017 15:34:11 -0800 Subject: [PATCH 5/7] ci(travis): only build master and PRs --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1301d4a..cb4bcbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,5 +56,5 @@ stages: if: branch = master AND type = push AND fork = false branches: - except: - - /^v\d+\.\d+\.\d+$/ + only: + - master From 20960a8b9f6fb96d8ec0fb632f5b0d629a44da21 Mon Sep 17 00:00:00 2001 From: Felix Becker Date: Sat, 30 Dec 2017 22:26:51 -0800 Subject: [PATCH 6/7] fix(DefinitionResolver): find variables in sibling children (#568) Fixes #566 --- fixtures/completion/foreach.php | 6 +++ src/DefinitionResolver.php | 73 ++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/fixtures/completion/foreach.php b/fixtures/completion/foreach.php index 4278f00..84bafd1 100644 --- a/fixtures/completion/foreach.php +++ b/fixtures/completion/foreach.php @@ -38,3 +38,9 @@ foreach ($bar->test() as $value) { foreach ($unknownArray as $member->access => $unknown) { $unkno + +foreach ($loop as $loop) { +} + +foreach ($loop->getArray() as $loop) { +} diff --git a/src/DefinitionResolver.php b/src/DefinitionResolver.php index 72bdce4..f5e5f7b 100644 --- a/src/DefinitionResolver.php +++ b/src/DefinitionResolver.php @@ -545,6 +545,15 @@ class DefinitionResolver } else { throw new \InvalidArgumentException('$var must be Variable, Param or ClosureUse, not ' . get_class($var)); } + if (empty($name)) { + return null; + } + + $shouldDescend = function ($nodeToDescand) { + // Make sure not to decend into functions or classes (they represent a scope boundary) + return !($nodeToDescand instanceof PhpParser\FunctionLike || $nodeToDescand instanceof PhpParser\ClassLike); + }; + // Traverse the AST up do { // If a function is met, check the parameters and use statements @@ -569,39 +578,55 @@ class DefinitionResolver break; } - // If we get to a ForeachStatement, check the keys and values - if ($n instanceof Node\Statement\ForeachStatement) { - if ($n->foreachKey instanceof Node\Expression\Variable - && $n->foreachKey->expression->getName() === $name - ) { - return $n->foreachKey; - } - if ($n->foreachValue - && $n->foreachValue->expression instanceof Node\Expression\Variable - && $n->foreachValue->expression->getName() === $name - ) { - return $n->foreachValue; - } - } - - // Check each previous sibling node for a variable assignment to that variable + // Check each previous sibling node and their descendents for a variable assignment to that variable + // Each previous sibling could contain a declaration of the variable while (($prevSibling = $n->getPreviousSibling()) !== null && $n = $prevSibling) { - if ($n instanceof Node\Statement\ExpressionStatement) { - $n = $n->expression; - } - if ( - // TODO - clean this up - ($n instanceof Node\Expression\AssignmentExpression && $n->operator->kind === PhpParser\TokenKind::EqualsToken) - && $n->leftOperand instanceof Node\Expression\Variable && $n->leftOperand->getName() === $name - ) { + + // Check the sibling itself + if (self::isVariableDeclaration($n, $name)) { return $n; } + + // Check descendant of this sibling (e.g. the children of a previous if block) + foreach ($n->getDescendantNodes($shouldDescend) as $descendant) { + if (self::isVariableDeclaration($descendant, $name)) { + return $descendant; + } + } } } while (isset($n) && $n = $n->parent); // Return null if nothing was found return null; } + /** + * Checks whether the given Node declares the given variable name + * + * @param Node $n The Node to check + * @param string $name The name of the wanted variable + * @return bool + */ + private static function isVariableDeclaration(Node $n, string $name) + { + if ( + // TODO - clean this up + ($n instanceof Node\Expression\AssignmentExpression && $n->operator->kind === PhpParser\TokenKind::EqualsToken) + && $n->leftOperand instanceof Node\Expression\Variable && $n->leftOperand->getName() === $name + ) { + return true; + } + + if ( + ($n instanceof Node\ForeachValue || $n instanceof Node\ForeachKey) + && $n->expression instanceof Node\Expression\Variable + && $n->expression->getName() === $name + ) { + return true; + } + + return false; + } + /** * Given an expression node, resolves that expression recursively to a type. * If the type could not be resolved, returns Types\Mixed_. From c48ee5580878b73d3f0e9c61b2198ec97df3dee6 Mon Sep 17 00:00:00 2001 From: Tyson Andre Date: Mon, 1 Jan 2018 18:31:55 -0800 Subject: [PATCH 7/7] tests: fix benchmark on case sensitive filesystems (#573) On case insensitive file systems, such as the defaults for Mac OS/Windows, this works, but it doesn't work for ext4, etc. The folder being checked out is `validation/frameworks/codeigniter`, this searched for `validation/frameworks/CodeIgniter` --- Performance.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Performance.php b/Performance.php index 5022f84..6fc1c28 100644 --- a/Performance.php +++ b/Performance.php @@ -14,7 +14,7 @@ use RecursiveIteratorIterator; $totalSize = 0; -$frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "CodeIgniter", "cakephp"]; +$frameworks = ["drupal", "wordpress", "php-language-server", "tolerant-php-parser", "math-php", "symfony", "codeigniter", "cakephp"]; foreach($frameworks as $framework) { $iterator = new RecursiveDirectoryIterator(__DIR__ . "/validation/frameworks/$framework");