-
Notifications
You must be signed in to change notification settings - Fork 187
Read vendor directory from project's composer.json, if set. #281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read vendor directory from project's composer.json, if set. #281
Conversation
If not set, default to "vendor". Also sort found vendor directories so we obtain the correct composer.json and composer.lock files
9e2a675 to
f43e0dc
Compare
Codecov Report
@@ Coverage Diff @@
## master #281 +/- ##
============================================
- Coverage 86.1% 86.07% -0.03%
+ Complexity 723 718 -5
============================================
Files 54 53 -1
Lines 1461 1458 -3
============================================
- Hits 1258 1255 -3
Misses 203 203
Continue to review full report at Codecov.
|
felixfbecker
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other places in the language server where a vendor directory is assumed, those would have to be changed too.
src/Indexer.php
Outdated
| /** @var string[][] */ | ||
| $deps = []; | ||
|
|
||
| $vendorDir = str_replace('/', '\/', @$this->composerJson->config->{'vendor-dir'} ?: 'vendor'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't use the @ operator, check this with if/isset
src/Indexer.php
Outdated
| $this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache"); | ||
| $this->cache->set($cacheKey, $index); | ||
| } else { | ||
| $this->client->window->logMessage(MessageType::INFO, "Cannot cache $packageName, cache key was null. Either you are using a 'dev-' version or your composer.lock is missing references."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a warning and shorten text to Could not compute cache key for $packageName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought including a potential reason would help some people. I can shorten it though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logs are meant for developers and admins
src/LanguageServer.php
Outdated
| $composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath)); | ||
|
|
||
| // If we sort our findings by string length (shortest to longest), | ||
| // the first entry will be the project's root composer.json. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not true
/myproject/http_rest_api_backend/composer.json
/myproject/vendor/psr/log/composer.json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, should have thought about it for more than two seconds. I think substr_count($a, '/') will do it correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would work! 👍
But you need to apply it to the path component of the URI, otherwise a URL with host will have 2 more slashes than a URN. Use Uri\parse()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a Uri class included. Do you have a parsing library to recommend adding to the project?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will need to add use Sabre\Uri
Change log message
src/utils.php
Outdated
| use InvalidArgumentException; | ||
| use PhpParser\Node; | ||
| use Sabre\Event\{Loop, Promise, EmitterInterface}; | ||
| use function Sabre\Uri\parse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please only alias Sabre\Uri and reference with Uri\parse(), otherwise it is harder to understand what is going on.
src/utils.php
Outdated
| * @param $b string | ||
| * @return integer | ||
| */ | ||
| function sortByLeastSlashes($a, $b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this function take the array and call usort on it
rename to sortUrisLevelOrder, it makes it more clear what it does
src/LanguageServer.php
Outdated
| use Throwable; | ||
| use Webmozart\PathUtil\Path; | ||
| use Sabre\Uri; | ||
| use function LanguageServer\sortByLeastSlashes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the function is in the same namespace
felixfbecker
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also look for the other places where vendor is assumed?
src/utils.php
Outdated
| * @param $b string | ||
| * @return integer | ||
| * @param array | ||
| * @return array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@return void
| { | ||
| return substr_count(parse($a)['path'], '/') - substr_count(parse($b)['path'], '/'); | ||
| usort($uriList, function ($a, $b) { | ||
| return substr_count(Uri\parse($a)['path'], '/') - substr_count(Uri\parse($b)['path'], '/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only parse the URI once
|
There are a few places where |
I think TextDocument will be straightforward but not PhpDocument. Workspace might be doable simply, too. Looking into it now. |
|
The |
src/utils.php
Outdated
| // Parse URIs so we are not continually parsing them while sorting | ||
| $parsedUriList = array_map(function ($uri) { | ||
| return Uri\parse($uri); | ||
| }, $uriList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry, I had a brain fart in my last review. I though $a and $b were the same, but they are not.
This is just micro optimisation, you can git revert it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha ok I was wondering like, "does he have a use-case with a million files?" 🎈
src/Index/ProjectIndex.php
Outdated
| public function getIndexForUri(string $uri): Index | ||
| { | ||
| if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) { | ||
| if (\LanguageServer\uriInVendorDir($this->composerJson, $uri, $matches)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alias the function
src/utils.php
Outdated
| * @param array $matches | ||
| * @return int | ||
| */ | ||
| function uriInVendorDir(\stdClass $composerJson = null, string $uri, &$matches): int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you rename this to getPackageName and make it return a string or null?
tests/PhpDocumentTest.php
Outdated
| { | ||
| $document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;"); | ||
| $this->assertEquals(true, $document->isVendored()); | ||
| $this->assertEquals(true, \LanguageServer\isVendored($document)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alias the function
src/utils.php
Outdated
| */ | ||
| function getVendorDir(\stdClass $composerJson = null): string | ||
| { | ||
| return isset($composerJson->config->{'vendor-dir'}) ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think config could be undefined too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should work correctly, see the following example:
$ psysh
Psy Shell v0.7.1 (PHP 7.1.1 — cli) by Justin Hileman
>>> $a = new \stdClass;
=> {#197}
>>> $a->foo->bar = 1
PHP warning: Creating default object from empty value on line 1
>>> $a
=> {#197
+"foo": {#200
+"bar": 1,
},
}
>>> isset($a->foo->bar);
=> true
>>> isset($a->foo->baz);
=> false
>>> isset($a->foz->baz);
=> false
// isset operates just like @ and suppresses errors for the whole line (in parens anyway)
felixfbecker
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost LGTM
src/Server/Workspace.php
Outdated
| use Sabre\Event\Promise; | ||
| use function Sabre\Event\coroutine; | ||
| use function LanguageServer\waitForEvent; | ||
| use function LanguageServer\getPackageName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use a group use statement
src/utils.php
Outdated
| * @return string | ||
| */ | ||
| function uriInVendorDir(\stdClass $composerJson = null, string $uri, &$matches): int | ||
| function getPackageName(\stdClass $composerJson = null, string $uri): ?string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am pretty sure the return syntax you are using is only available in PHP7.1. That would be a breaking change, please omit the return type annotation
src/utils.php
Outdated
| * @param string $uri | ||
| * @param array $matches | ||
| * @return int | ||
| * @return string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be @return string|null
src/utils.php
Outdated
| $vendorDir = str_replace('/', '\/', getVendorDir($composerJson)); | ||
| return preg_match("/\/$vendorDir\/([^\/]+\/[^\/]+)\//", $uri, $matches); | ||
| preg_match("/\/$vendorDir\/([^\/]+\/[^\/]+)\//", $uri, $matches); | ||
| return isset($matches[1]) ? $matches[1] : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $matches[1] ?? null;
src/Server/TextDocument.php
Outdated
| { | ||
| $document = $this->documentLoader->open($textDocument->uri, $textDocument->text); | ||
| if (!$document->isVendored()) { | ||
| if (!\LanguageServer\isVendored($document, $this->composerJson)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alias the function
src/utils.php
Outdated
| * @param array $matches | ||
| * @return string | ||
| */ | ||
| function getPackageName(\stdClass $composerJson = null, string $uri): ?string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you change this to take $composerJson as second parameter, so it's consistent with isVendored()?
missed a vendor reference!
|
Lotta commits in here, might want to squash and merge so it's cleaner (I usually rebase and squash for my own work) |
|
Thanks! |
|
Thank you! Happy to collaborate with a responsive maintainer. |
If not set, default to "vendor".
This PR also includes a bugfix for finding the appropriate composer.json and composer.lock files.
This PR does not address an existing issue with files that are included in composer.json via the "repositories" mechanism with a type of "package" mechanism, e.g.:
{ "type": "package", "package": { "name": "ezyang/htmlpurifier", "version": "4.6.0", "dist": { "url": "https://github.com/ezyang/HTMLPurifier/archive/v4.6.0.zip", "type": "zip" } } }These are not currently cached. Those files are stored in the
[vendor]/composer/[some hash]/[packageName]-[packageVersion]directory. A future enhancement could cache these files as well.