Read vendor directory from project's composer.json, if set. (#281)
parent
571b26a0c3
commit
d5c54ac30f
|
@ -3,6 +3,8 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace LanguageServer\Index;
|
namespace LanguageServer\Index;
|
||||||
|
|
||||||
|
use function LanguageServer\getPackageName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A project index manages the source and dependency indexes
|
* A project index manages the source and dependency indexes
|
||||||
*/
|
*/
|
||||||
|
@ -22,10 +24,11 @@ class ProjectIndex extends AbstractAggregateIndex
|
||||||
*/
|
*/
|
||||||
private $sourceIndex;
|
private $sourceIndex;
|
||||||
|
|
||||||
public function __construct(Index $sourceIndex, DependenciesIndex $dependenciesIndex)
|
public function __construct(Index $sourceIndex, DependenciesIndex $dependenciesIndex, \stdClass $composerJson = null)
|
||||||
{
|
{
|
||||||
$this->sourceIndex = $sourceIndex;
|
$this->sourceIndex = $sourceIndex;
|
||||||
$this->dependenciesIndex = $dependenciesIndex;
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
|
$this->composerJson = $composerJson;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +46,8 @@ class ProjectIndex extends AbstractAggregateIndex
|
||||||
*/
|
*/
|
||||||
public function getIndexForUri(string $uri): Index
|
public function getIndexForUri(string $uri): Index
|
||||||
{
|
{
|
||||||
if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
|
$packageName = getPackageName($uri, $this->composerJson);
|
||||||
$packageName = $matches[1];
|
if ($packageName) {
|
||||||
return $this->dependenciesIndex->getDependencyIndex($packageName);
|
return $this->dependenciesIndex->getDependencyIndex($packageName);
|
||||||
}
|
}
|
||||||
return $this->sourceIndex;
|
return $this->sourceIndex;
|
||||||
|
|
|
@ -59,6 +59,11 @@ class Indexer
|
||||||
*/
|
*/
|
||||||
private $composerLock;
|
private $composerLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \stdClasss
|
||||||
|
*/
|
||||||
|
private $composerJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FilesFinder $filesFinder
|
* @param FilesFinder $filesFinder
|
||||||
* @param string $rootPath
|
* @param string $rootPath
|
||||||
|
@ -77,7 +82,8 @@ class Indexer
|
||||||
DependenciesIndex $dependenciesIndex,
|
DependenciesIndex $dependenciesIndex,
|
||||||
Index $sourceIndex,
|
Index $sourceIndex,
|
||||||
PhpDocumentLoader $documentLoader,
|
PhpDocumentLoader $documentLoader,
|
||||||
\stdClass $composerLock = null
|
\stdClass $composerLock = null,
|
||||||
|
\stdClass $composerJson = null
|
||||||
) {
|
) {
|
||||||
$this->filesFinder = $filesFinder;
|
$this->filesFinder = $filesFinder;
|
||||||
$this->rootPath = $rootPath;
|
$this->rootPath = $rootPath;
|
||||||
|
@ -87,6 +93,7 @@ class Indexer
|
||||||
$this->sourceIndex = $sourceIndex;
|
$this->sourceIndex = $sourceIndex;
|
||||||
$this->documentLoader = $documentLoader;
|
$this->documentLoader = $documentLoader;
|
||||||
$this->composerLock = $composerLock;
|
$this->composerLock = $composerLock;
|
||||||
|
$this->composerJson = $composerJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,10 +116,11 @@ class Indexer
|
||||||
$source = [];
|
$source = [];
|
||||||
/** @var string[][] */
|
/** @var string[][] */
|
||||||
$deps = [];
|
$deps = [];
|
||||||
|
|
||||||
foreach ($uris as $uri) {
|
foreach ($uris as $uri) {
|
||||||
if ($this->composerLock !== null && preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
|
$packageName = getPackageName($uri, $this->composerJson);
|
||||||
|
if ($this->composerLock !== null && $packageName) {
|
||||||
// Dependency file
|
// Dependency file
|
||||||
$packageName = $matches[1];
|
|
||||||
if (!isset($deps[$packageName])) {
|
if (!isset($deps[$packageName])) {
|
||||||
$deps[$packageName] = [];
|
$deps[$packageName] = [];
|
||||||
}
|
}
|
||||||
|
@ -174,6 +182,8 @@ class Indexer
|
||||||
if ($cacheKey !== null) {
|
if ($cacheKey !== null) {
|
||||||
$this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
|
$this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
|
||||||
$this->cache->set($cacheKey, $index);
|
$this->cache->set($cacheKey, $index);
|
||||||
|
} else {
|
||||||
|
$this->client->window->logMessage(MessageType::WARNING, "Could not compute cache key for $packageName");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +215,7 @@ class Indexer
|
||||||
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
|
||||||
try {
|
try {
|
||||||
$document = yield $this->documentLoader->load($uri);
|
$document = yield $this->documentLoader->load($uri);
|
||||||
if (!$document->isVendored()) {
|
if (!isVendored($document, $this->composerJson)) {
|
||||||
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
|
||||||
}
|
}
|
||||||
} catch (ContentTooLargeException $e) {
|
} catch (ContentTooLargeException $e) {
|
||||||
|
|
|
@ -187,7 +187,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
|
|
||||||
$dependenciesIndex = new DependenciesIndex;
|
$dependenciesIndex = new DependenciesIndex;
|
||||||
$sourceIndex = new Index;
|
$sourceIndex = new Index;
|
||||||
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex);
|
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex, $this->composerJson);
|
||||||
$stubsIndex = StubsIndex::read();
|
$stubsIndex = StubsIndex::read();
|
||||||
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
|
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
|
||||||
|
|
||||||
|
@ -206,6 +206,8 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
// Find composer.json
|
// Find composer.json
|
||||||
if ($this->composerJson === null) {
|
if ($this->composerJson === null) {
|
||||||
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
|
||||||
|
sortUrisLevelOrder($composerJsonFiles);
|
||||||
|
|
||||||
if (!empty($composerJsonFiles)) {
|
if (!empty($composerJsonFiles)) {
|
||||||
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
|
||||||
}
|
}
|
||||||
|
@ -214,6 +216,8 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
// Find composer.lock
|
// Find composer.lock
|
||||||
if ($this->composerLock === null) {
|
if ($this->composerLock === null) {
|
||||||
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
|
||||||
|
sortUrisLevelOrder($composerLockFiles);
|
||||||
|
|
||||||
if (!empty($composerLockFiles)) {
|
if (!empty($composerLockFiles)) {
|
||||||
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
|
||||||
}
|
}
|
||||||
|
@ -230,7 +234,8 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$dependenciesIndex,
|
$dependenciesIndex,
|
||||||
$sourceIndex,
|
$sourceIndex,
|
||||||
$this->documentLoader,
|
$this->documentLoader,
|
||||||
$this->composerLock
|
$this->composerLock,
|
||||||
|
$this->composerJson
|
||||||
);
|
);
|
||||||
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
$indexer->index()->otherwise('\\LanguageServer\\crash');
|
||||||
}
|
}
|
||||||
|
@ -252,7 +257,8 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
|
||||||
$dependenciesIndex,
|
$dependenciesIndex,
|
||||||
$sourceIndex,
|
$sourceIndex,
|
||||||
$this->composerLock,
|
$this->composerLock,
|
||||||
$this->documentLoader
|
$this->documentLoader,
|
||||||
|
$this->composerJson
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,17 +220,6 @@ class PhpDocument
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the document is a dependency
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function isVendored(): bool
|
|
||||||
{
|
|
||||||
$path = Uri\parse($this->uri)['path'];
|
|
||||||
return strpos($path, '/vendor/') !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of TextEdit changes to format this document.
|
* Returns array of TextEdit changes to format this document.
|
||||||
*
|
*
|
||||||
|
|
|
@ -30,7 +30,7 @@ use LanguageServer\Index\ReadableIndex;
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use Sabre\Uri;
|
use Sabre\Uri;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
use function LanguageServer\waitForEvent;
|
use function LanguageServer\{waitForEvent, isVendored};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all textDocument/* methods
|
* Provides method handlers for all textDocument/* methods
|
||||||
|
@ -134,7 +134,7 @@ class TextDocument
|
||||||
public function didOpen(TextDocumentItem $textDocument)
|
public function didOpen(TextDocumentItem $textDocument)
|
||||||
{
|
{
|
||||||
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
|
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
|
||||||
if (!$document->isVendored()) {
|
if (!isVendored($document, $this->composerJson)) {
|
||||||
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,9 +409,9 @@ class TextDocument
|
||||||
$symbol->$prop = $val;
|
$symbol->$prop = $val;
|
||||||
}
|
}
|
||||||
$symbol->fqsen = $def->fqn;
|
$symbol->fqsen = $def->fqn;
|
||||||
if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches) && $this->composerLock !== null) {
|
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
||||||
|
if ($packageName && $this->composerLock !== null) {
|
||||||
// Definition is inside a dependency
|
// Definition is inside a dependency
|
||||||
$packageName = $matches[1];
|
|
||||||
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
||||||
if ($package->name === $packageName) {
|
if ($package->name === $packageName) {
|
||||||
$symbol->package = $package;
|
$symbol->package = $package;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use LanguageServer\Index\{ProjectIndex, DependenciesIndex, Index};
|
||||||
use LanguageServer\Protocol\{SymbolInformation, SymbolDescriptor, ReferenceInformation, DependencyReference, Location};
|
use LanguageServer\Protocol\{SymbolInformation, SymbolDescriptor, ReferenceInformation, DependencyReference, Location};
|
||||||
use Sabre\Event\Promise;
|
use Sabre\Event\Promise;
|
||||||
use function Sabre\Event\coroutine;
|
use function Sabre\Event\coroutine;
|
||||||
use function LanguageServer\waitForEvent;
|
use function LanguageServer\{waitForEvent, getPackageName};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides method handlers for all workspace/* methods
|
* Provides method handlers for all workspace/* methods
|
||||||
|
@ -49,13 +49,14 @@ class Workspace
|
||||||
* @param \stdClass $composerLock The parsed composer.lock of the project, if any
|
* @param \stdClass $composerLock The parsed composer.lock of the project, if any
|
||||||
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents
|
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents
|
||||||
*/
|
*/
|
||||||
public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader)
|
public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null)
|
||||||
{
|
{
|
||||||
$this->sourceIndex = $sourceIndex;
|
$this->sourceIndex = $sourceIndex;
|
||||||
$this->index = $index;
|
$this->index = $index;
|
||||||
$this->dependenciesIndex = $dependenciesIndex;
|
$this->dependenciesIndex = $dependenciesIndex;
|
||||||
$this->composerLock = $composerLock;
|
$this->composerLock = $composerLock;
|
||||||
$this->documentLoader = $documentLoader;
|
$this->documentLoader = $documentLoader;
|
||||||
|
$this->composerJson = $composerJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,8 +123,7 @@ class Workspace
|
||||||
$symbol->$prop = $val;
|
$symbol->$prop = $val;
|
||||||
}
|
}
|
||||||
// Find out package name
|
// Find out package name
|
||||||
preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches);
|
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
|
||||||
$packageName = $matches[1];
|
|
||||||
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
|
||||||
if ($package->name === $packageName) {
|
if ($package->name === $packageName) {
|
||||||
$symbol->package = $package;
|
$symbol->package = $package;
|
||||||
|
|
|
@ -7,6 +7,7 @@ use Throwable;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use Sabre\Event\{Loop, Promise, EmitterInterface};
|
use Sabre\Event\{Loop, Promise, EmitterInterface};
|
||||||
|
use Sabre\Uri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms an absolute file path into a URI as used by the language server protocol.
|
* Transforms an absolute file path into a URI as used by the language server protocol.
|
||||||
|
@ -131,3 +132,60 @@ function stripStringOverlap(string $a, string $b): string
|
||||||
}
|
}
|
||||||
return $b;
|
return $b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use for sorting an array of URIs by number of segments
|
||||||
|
* in ascending order.
|
||||||
|
*
|
||||||
|
* @param array $uriList
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function sortUrisLevelOrder(&$uriList)
|
||||||
|
{
|
||||||
|
usort($uriList, function ($a, $b) {
|
||||||
|
return substr_count(Uri\parse($a)['path'], '/') - substr_count(Uri\parse($b)['path'], '/');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks a document against the composer.json to see if it
|
||||||
|
* is a vendored document
|
||||||
|
*
|
||||||
|
* @param PhpDocument $document
|
||||||
|
* @param \stdClass|null $composerJson
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool
|
||||||
|
{
|
||||||
|
$path = Uri\parse($document->getUri())['path'];
|
||||||
|
$vendorDir = getVendorDir($composerJson);
|
||||||
|
return strpos($path, "/$vendorDir/") !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a given URI against the composer.json to see if it
|
||||||
|
* is a vendored URI
|
||||||
|
*
|
||||||
|
* @param \stdClass|null $composerJson
|
||||||
|
* @param string $uri
|
||||||
|
* @param array $matches
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
function getPackageName(string $uri, \stdClass $composerJson = null)
|
||||||
|
{
|
||||||
|
$vendorDir = str_replace('/', '\/', getVendorDir($composerJson));
|
||||||
|
preg_match("/\/$vendorDir\/([^\/]+\/[^\/]+)\//", $uri, $matches);
|
||||||
|
return $matches[1] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get the vendor directory from composer.json
|
||||||
|
* or default to 'vendor'
|
||||||
|
*
|
||||||
|
* @param \stdClass|null $composerJson
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getVendorDir(\stdClass $composerJson = null): string
|
||||||
|
{
|
||||||
|
return $composerJson->config->{'vendor-dir'} ?? 'vendor';
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ use LanguageServer\ContentRetriever\FileSystemContentRetriever;
|
||||||
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
|
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
|
||||||
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
use function LanguageServer\isVendored;
|
||||||
|
|
||||||
class PhpDocumentTest extends TestCase
|
class PhpDocumentTest extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -42,18 +43,18 @@ class PhpDocumentTest extends TestCase
|
||||||
public function testIsVendored()
|
public function testIsVendored()
|
||||||
{
|
{
|
||||||
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, isVendored($document));
|
||||||
|
|
||||||
$document = $this->createDocument('file:///c:/dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///c:/dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, isVendored($document));
|
||||||
|
|
||||||
$document = $this->createDocument('file:///vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///vendor/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(true, $document->isVendored());
|
$this->assertEquals(true, isVendored($document));
|
||||||
|
|
||||||
$document = $this->createDocument('file:///dir/vendor.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/vendor.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(false, $document->isVendored());
|
$this->assertEquals(false, isVendored($document));
|
||||||
|
|
||||||
$document = $this->createDocument('file:///dir/x.php', "<?php\n$\$a = new SomeClass;");
|
$document = $this->createDocument('file:///dir/x.php', "<?php\n$\$a = new SomeClass;");
|
||||||
$this->assertEquals(false, $document->isVendored());
|
$this->assertEquals(false, isVendored($document));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue