Skip to content

Commit d5c54ac

Browse files
wulftonefelixfbecker
authored andcommitted
Read vendor directory from project's composer.json, if set. (#281)
1 parent 571b26a commit d5c54ac

File tree

8 files changed

+101
-34
lines changed

8 files changed

+101
-34
lines changed

src/Index/ProjectIndex.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace LanguageServer\Index;
55

6+
use function LanguageServer\getPackageName;
7+
68
/**
79
* A project index manages the source and dependency indexes
810
*/
@@ -22,10 +24,11 @@ class ProjectIndex extends AbstractAggregateIndex
2224
*/
2325
private $sourceIndex;
2426

25-
public function __construct(Index $sourceIndex, DependenciesIndex $dependenciesIndex)
27+
public function __construct(Index $sourceIndex, DependenciesIndex $dependenciesIndex, \stdClass $composerJson = null)
2628
{
2729
$this->sourceIndex = $sourceIndex;
2830
$this->dependenciesIndex = $dependenciesIndex;
31+
$this->composerJson = $composerJson;
2932
parent::__construct();
3033
}
3134

@@ -43,8 +46,8 @@ protected function getIndexes(): array
4346
*/
4447
public function getIndexForUri(string $uri): Index
4548
{
46-
if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
47-
$packageName = $matches[1];
49+
$packageName = getPackageName($uri, $this->composerJson);
50+
if ($packageName) {
4851
return $this->dependenciesIndex->getDependencyIndex($packageName);
4952
}
5053
return $this->sourceIndex;

src/Indexer.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class Indexer
5959
*/
6060
private $composerLock;
6161

62+
/**
63+
* @var \stdClasss
64+
*/
65+
private $composerJson;
66+
6267
/**
6368
* @param FilesFinder $filesFinder
6469
* @param string $rootPath
@@ -77,7 +82,8 @@ public function __construct(
7782
DependenciesIndex $dependenciesIndex,
7883
Index $sourceIndex,
7984
PhpDocumentLoader $documentLoader,
80-
\stdClass $composerLock = null
85+
\stdClass $composerLock = null,
86+
\stdClass $composerJson = null
8187
) {
8288
$this->filesFinder = $filesFinder;
8389
$this->rootPath = $rootPath;
@@ -87,6 +93,7 @@ public function __construct(
8793
$this->sourceIndex = $sourceIndex;
8894
$this->documentLoader = $documentLoader;
8995
$this->composerLock = $composerLock;
96+
$this->composerJson = $composerJson;
9097
}
9198

9299
/**
@@ -109,10 +116,11 @@ public function index(): Promise
109116
$source = [];
110117
/** @var string[][] */
111118
$deps = [];
119+
112120
foreach ($uris as $uri) {
113-
if ($this->composerLock !== null && preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $uri, $matches)) {
121+
$packageName = getPackageName($uri, $this->composerJson);
122+
if ($this->composerLock !== null && $packageName) {
114123
// Dependency file
115-
$packageName = $matches[1];
116124
if (!isset($deps[$packageName])) {
117125
$deps[$packageName] = [];
118126
}
@@ -174,6 +182,8 @@ public function index(): Promise
174182
if ($cacheKey !== null) {
175183
$this->client->window->logMessage(MessageType::INFO, "Storing $packageKey in cache");
176184
$this->cache->set($cacheKey, $index);
185+
} else {
186+
$this->client->window->logMessage(MessageType::WARNING, "Could not compute cache key for $packageName");
177187
}
178188
}
179189
}
@@ -205,7 +215,7 @@ private function indexFiles(array $files): Promise
205215
$this->client->window->logMessage(MessageType::LOG, "Parsing $uri");
206216
try {
207217
$document = yield $this->documentLoader->load($uri);
208-
if (!$document->isVendored()) {
218+
if (!isVendored($document, $this->composerJson)) {
209219
$this->client->textDocument->publishDiagnostics($uri, $document->getDiagnostics());
210220
}
211221
} catch (ContentTooLargeException $e) {

src/LanguageServer.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
187187

188188
$dependenciesIndex = new DependenciesIndex;
189189
$sourceIndex = new Index;
190-
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex);
190+
$this->projectIndex = new ProjectIndex($sourceIndex, $dependenciesIndex, $this->composerJson);
191191
$stubsIndex = StubsIndex::read();
192192
$this->globalIndex = new GlobalIndex($stubsIndex, $this->projectIndex);
193193

@@ -206,6 +206,8 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
206206
// Find composer.json
207207
if ($this->composerJson === null) {
208208
$composerJsonFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.json', $rootPath));
209+
sortUrisLevelOrder($composerJsonFiles);
210+
209211
if (!empty($composerJsonFiles)) {
210212
$this->composerJson = json_decode(yield $this->contentRetriever->retrieve($composerJsonFiles[0]));
211213
}
@@ -214,6 +216,8 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
214216
// Find composer.lock
215217
if ($this->composerLock === null) {
216218
$composerLockFiles = yield $this->filesFinder->find(Path::makeAbsolute('**/composer.lock', $rootPath));
219+
sortUrisLevelOrder($composerLockFiles);
220+
217221
if (!empty($composerLockFiles)) {
218222
$this->composerLock = json_decode(yield $this->contentRetriever->retrieve($composerLockFiles[0]));
219223
}
@@ -230,7 +234,8 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
230234
$dependenciesIndex,
231235
$sourceIndex,
232236
$this->documentLoader,
233-
$this->composerLock
237+
$this->composerLock,
238+
$this->composerJson
234239
);
235240
$indexer->index()->otherwise('\\LanguageServer\\crash');
236241
}
@@ -252,7 +257,8 @@ public function initialize(ClientCapabilities $capabilities, string $rootPath =
252257
$dependenciesIndex,
253258
$sourceIndex,
254259
$this->composerLock,
255-
$this->documentLoader
260+
$this->documentLoader,
261+
$this->composerJson
256262
);
257263
}
258264

src/PhpDocument.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,6 @@ public function updateContent(string $content)
220220
}
221221
}
222222

223-
/**
224-
* Returns true if the document is a dependency
225-
*
226-
* @return bool
227-
*/
228-
public function isVendored(): bool
229-
{
230-
$path = Uri\parse($this->uri)['path'];
231-
return strpos($path, '/vendor/') !== false;
232-
}
233-
234223
/**
235224
* Returns array of TextEdit changes to format this document.
236225
*

src/Server/TextDocument.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
use Sabre\Event\Promise;
3131
use Sabre\Uri;
3232
use function Sabre\Event\coroutine;
33-
use function LanguageServer\waitForEvent;
33+
use function LanguageServer\{waitForEvent, isVendored};
3434

3535
/**
3636
* Provides method handlers for all textDocument/* methods
@@ -134,7 +134,7 @@ public function documentSymbol(TextDocumentIdentifier $textDocument): Promise
134134
public function didOpen(TextDocumentItem $textDocument)
135135
{
136136
$document = $this->documentLoader->open($textDocument->uri, $textDocument->text);
137-
if (!$document->isVendored()) {
137+
if (!isVendored($document, $this->composerJson)) {
138138
$this->client->textDocument->publishDiagnostics($textDocument->uri, $document->getDiagnostics());
139139
}
140140
}
@@ -409,9 +409,9 @@ public function xdefinition(TextDocumentIdentifier $textDocument, Position $posi
409409
$symbol->$prop = $val;
410410
}
411411
$symbol->fqsen = $def->fqn;
412-
if (preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches) && $this->composerLock !== null) {
412+
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
413+
if ($packageName && $this->composerLock !== null) {
413414
// Definition is inside a dependency
414-
$packageName = $matches[1];
415415
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
416416
if ($package->name === $packageName) {
417417
$symbol->package = $package;

src/Server/Workspace.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use LanguageServer\Protocol\{SymbolInformation, SymbolDescriptor, ReferenceInformation, DependencyReference, Location};
99
use Sabre\Event\Promise;
1010
use function Sabre\Event\coroutine;
11-
use function LanguageServer\waitForEvent;
11+
use function LanguageServer\{waitForEvent, getPackageName};
1212

1313
/**
1414
* Provides method handlers for all workspace/* methods
@@ -49,13 +49,14 @@ class Workspace
4949
* @param \stdClass $composerLock The parsed composer.lock of the project, if any
5050
* @param PhpDocumentLoader $documentLoader PhpDocumentLoader instance to load documents
5151
*/
52-
public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader)
52+
public function __construct(ProjectIndex $index, DependenciesIndex $dependenciesIndex, Index $sourceIndex, \stdClass $composerLock = null, PhpDocumentLoader $documentLoader, \stdClass $composerJson = null)
5353
{
5454
$this->sourceIndex = $sourceIndex;
5555
$this->index = $index;
5656
$this->dependenciesIndex = $dependenciesIndex;
5757
$this->composerLock = $composerLock;
5858
$this->documentLoader = $documentLoader;
59+
$this->composerJson = $composerJson;
5960
}
6061

6162
/**
@@ -122,8 +123,7 @@ public function xreferences($query, array $files = null): Promise
122123
$symbol->$prop = $val;
123124
}
124125
// Find out package name
125-
preg_match('/\/vendor\/([^\/]+\/[^\/]+)\//', $def->symbolInformation->location->uri, $matches);
126-
$packageName = $matches[1];
126+
$packageName = getPackageName($def->symbolInformation->location->uri, $this->composerJson);
127127
foreach (array_merge($this->composerLock->packages, $this->composerLock->{'packages-dev'}) as $package) {
128128
if ($package->name === $packageName) {
129129
$symbol->package = $package;

src/utils.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use InvalidArgumentException;
88
use PhpParser\Node;
99
use Sabre\Event\{Loop, Promise, EmitterInterface};
10+
use Sabre\Uri;
1011

1112
/**
1213
* 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
131132
}
132133
return $b;
133134
}
135+
136+
/**
137+
* Use for sorting an array of URIs by number of segments
138+
* in ascending order.
139+
*
140+
* @param array $uriList
141+
* @return void
142+
*/
143+
function sortUrisLevelOrder(&$uriList)
144+
{
145+
usort($uriList, function ($a, $b) {
146+
return substr_count(Uri\parse($a)['path'], '/') - substr_count(Uri\parse($b)['path'], '/');
147+
});
148+
}
149+
150+
/**
151+
* Checks a document against the composer.json to see if it
152+
* is a vendored document
153+
*
154+
* @param PhpDocument $document
155+
* @param \stdClass|null $composerJson
156+
* @return bool
157+
*/
158+
function isVendored(PhpDocument $document, \stdClass $composerJson = null): bool
159+
{
160+
$path = Uri\parse($document->getUri())['path'];
161+
$vendorDir = getVendorDir($composerJson);
162+
return strpos($path, "/$vendorDir/") !== false;
163+
}
164+
165+
/**
166+
* Check a given URI against the composer.json to see if it
167+
* is a vendored URI
168+
*
169+
* @param \stdClass|null $composerJson
170+
* @param string $uri
171+
* @param array $matches
172+
* @return string|null
173+
*/
174+
function getPackageName(string $uri, \stdClass $composerJson = null)
175+
{
176+
$vendorDir = str_replace('/', '\/', getVendorDir($composerJson));
177+
preg_match("/\/$vendorDir\/([^\/]+\/[^\/]+)\//", $uri, $matches);
178+
return $matches[1] ?? null;
179+
}
180+
181+
/**
182+
* Helper function to get the vendor directory from composer.json
183+
* or default to 'vendor'
184+
*
185+
* @param \stdClass|null $composerJson
186+
* @return string
187+
*/
188+
function getVendorDir(\stdClass $composerJson = null): string
189+
{
190+
return $composerJson->config->{'vendor-dir'} ?? 'vendor';
191+
}

tests/PhpDocumentTest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use LanguageServer\Protocol\{SymbolKind, Position, ClientCapabilities};
1313
use LanguageServer\Index\{Index, ProjectIndex, DependenciesIndex};
1414
use PhpParser\Node;
15+
use function LanguageServer\isVendored;
1516

1617
class PhpDocumentTest extends TestCase
1718
{
@@ -42,18 +43,18 @@ public function testGetNodeAtPosition()
4243
public function testIsVendored()
4344
{
4445
$document = $this->createDocument('file:///dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
45-
$this->assertEquals(true, $document->isVendored());
46+
$this->assertEquals(true, isVendored($document));
4647

4748
$document = $this->createDocument('file:///c:/dir/vendor/x.php', "<?php\n$\$a = new SomeClass;");
48-
$this->assertEquals(true, $document->isVendored());
49+
$this->assertEquals(true, isVendored($document));
4950

5051
$document = $this->createDocument('file:///vendor/x.php', "<?php\n$\$a = new SomeClass;");
51-
$this->assertEquals(true, $document->isVendored());
52+
$this->assertEquals(true, isVendored($document));
5253

5354
$document = $this->createDocument('file:///dir/vendor.php', "<?php\n$\$a = new SomeClass;");
54-
$this->assertEquals(false, $document->isVendored());
55+
$this->assertEquals(false, isVendored($document));
5556

5657
$document = $this->createDocument('file:///dir/x.php', "<?php\n$\$a = new SomeClass;");
57-
$this->assertEquals(false, $document->isVendored());
58+
$this->assertEquals(false, isVendored($document));
5859
}
5960
}

0 commit comments

Comments
 (0)