Skip to content

Conversation

@wulftone
Copy link
Contributor

@wulftone wulftone commented Feb 6, 2017

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.

If not set, default to "vendor".
Also sort found vendor directories so we obtain the correct
composer.json and composer.lock files
@wulftone wulftone force-pushed the allow-custom-vendor-dir branch from 9e2a675 to f43e0dc Compare February 6, 2017 04:26
@codecov
Copy link

codecov bot commented Feb 6, 2017

Codecov Report

Merging #281 into master will not impact coverage by -0.03%.

@@             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
Impacted Files Coverage Δ Complexity Δ
src/PhpDocument.php 84.33% <ø> (-0.55%) 28 <ø> (-1)
src/Server/TextDocument.php 98.01% <100%> (ø) 36 <ø> (ø)
src/Server/Workspace.php 88.23% <100%> (+0.73%) 24 <1> (ø)
src/utils.php 100% <100%> (ø) 0 <ø> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 571b26a...0ad8f34. Read the comment docs.

Copy link
Owner

@felixfbecker felixfbecker left a 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');
Copy link
Owner

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.");
Copy link
Owner

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

Copy link
Contributor Author

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.

Copy link
Owner

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

$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.
Copy link
Owner

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

Copy link
Contributor Author

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?

Copy link
Owner

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()

Copy link
Contributor Author

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?

Copy link
Owner

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

Trevor Bortins added 2 commits February 6, 2017 13:12
src/utils.php Outdated
use InvalidArgumentException;
use PhpParser\Node;
use Sabre\Event\{Loop, Promise, EmitterInterface};
use function Sabre\Uri\parse;
Copy link
Owner

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)
Copy link
Owner

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

use Throwable;
use Webmozart\PathUtil\Path;
use Sabre\Uri;
use function LanguageServer\sortByLeastSlashes;
Copy link
Owner

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

Copy link
Owner

@felixfbecker felixfbecker left a 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
Copy link
Owner

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'], '/');
Copy link
Owner

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

@wulftone
Copy link
Contributor Author

wulftone commented Feb 6, 2017

There are a few places where vendor occurs, but I'm at a loss for a good strategy to deal with all of them all. Some are embedded in interesting places and getting a reference to composerJson in there is not straightforward. I'd like to centralize the logic if possible, but if you could give me a bit of architectural direction that would be quite helpful...

@wulftone
Copy link
Contributor Author

wulftone commented Feb 6, 2017

src/PhpDocument.php
231:        return strpos($path, '/vendor/') !== false;

src/Server/TextDocument.php
412:            if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches) && $this->composerLock !== null) {

src/Server/Workspace.php
125:                    preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches);

I think TextDocument will be straightforward but not PhpDocument. Workspace might be doable simply, too. Looking into it now.

@felixfbecker
Copy link
Owner

The isVendored() method can be removed in favor of just checking from the outside on getUri(). The other places shouldn't be too hard to inject an instance of the composer.json file

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);
Copy link
Owner

@felixfbecker felixfbecker Feb 6, 2017

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 :)

Copy link
Contributor Author

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?" 🎈

public function getIndexForUri(string $uri): Index
{
if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
if (\LanguageServer\uriInVendorDir($this->composerJson, $uri, $matches)) {
Copy link
Owner

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
Copy link
Owner

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?

{
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
$this->assertEquals(true, $document->isVendored());
$this->assertEquals(true, \LanguageServer\isVendored($document));
Copy link
Owner

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'}) ?
Copy link
Owner

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.

Copy link
Contributor Author

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)

Copy link
Owner

@felixfbecker felixfbecker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost LGTM

use Sabre\Event\Promise;
use function Sabre\Event\coroutine;
use function LanguageServer\waitForEvent;
use function LanguageServer\getPackageName;
Copy link
Owner

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
Copy link
Owner

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
Copy link
Owner

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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return $matches[1] ?? null;

{
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
if (!$document->isVendored()) {
if (!\LanguageServer\isVendored($document, $this->composerJson)) {
Copy link
Owner

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
Copy link
Owner

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()?

@wulftone
Copy link
Contributor Author

wulftone commented Feb 7, 2017

Lotta commits in here, might want to squash and merge so it's cleaner (I usually rebase and squash for my own work)

@felixfbecker felixfbecker merged commit d5c54ac into felixfbecker:master Feb 7, 2017
@felixfbecker
Copy link
Owner

Thanks!

@wulftone
Copy link
Contributor Author

wulftone commented Feb 7, 2017

Thank you! Happy to collaborate with a responsive maintainer.

@wulftone wulftone deleted the allow-custom-vendor-dir branch February 7, 2017 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants