From b7b9b5aff96543c58b19ca75b9a8424faff89054 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:08:29 +0000 Subject: [PATCH 01/10] Support for indexing and analyzing phars --- .../Indexer/AbstractClassLikeIndexer.php | 4 +-- .../Adapter/Tolerant/TolerantIndexBuilder.php | 5 +-- lib/Indexer/Model/IndexJob.php | 32 +++++++++++++++++++ .../SearchIndex/ValidatingSearchIndex.php | 2 ++ .../Worse/IndexerClassSourceLocatorTest.php | 16 ++++++++++ .../Exception/TextDocumentNotFound.php | 2 +- .../FilesystemTextDocumentLocator.php | 2 +- .../Tests/Unit/TextDocumentUriTest.php | 6 ++++ .../WorseReflectionDefinitionLocator.php | 2 +- 9 files changed, 64 insertions(+), 7 deletions(-) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php index ef259ee9c5..68e26ff1a2 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php @@ -68,7 +68,7 @@ protected function getClassLikeRecord(string $type, Node $node, Index $index, Te if (empty($name)) { throw new CannotIndexNode(sprintf( 'Name is empty for file "%s"', - $document->uri()->path() + $document->uri()->__toString() )); } @@ -77,7 +77,7 @@ protected function getClassLikeRecord(string $type, Node $node, Index $index, Te /** @var ClassDeclaration|InterfaceDeclaration|EnumDeclaration|TraitDeclaration $node */ $record->setStart(ByteOffset::fromInt($node->name->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->name->getEndPosition())); - $record->setFilePath($document->uri()->path()); + $record->setFilePath($document->uri()->__toString()); $record->setType($type); return $record; diff --git a/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php b/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php index 58f2197a39..086bebb1cd 100644 --- a/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php +++ b/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php @@ -18,6 +18,7 @@ use Phpactor\Indexer\Model\Index; use Phpactor\Indexer\Model\IndexBuilder; use Phpactor\TextDocument\TextDocument; +use Phpactor\WorseReflection\Core\Util\NodeUtil; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -63,7 +64,7 @@ public function index(TextDocument $document): void $indexer->beforeParse($this->index, $document); } - $node = $this->parser->parseSourceFile($document->__toString(), $document->uri()->path()); + $node = $this->parser->parseSourceFile($document->__toString(), $document->uri()->__toString()); $this->indexNode($document, $node); } @@ -83,7 +84,7 @@ private function indexNode(TextDocument $document, Node $node): void $this->logger->warning(sprintf( 'Cannot index node of class "%s" in file "%s": %s', get_class($node), - $document->uri()->__toString(), + $document->uri()?->__toString() ?? 'unknown', $cannotIndexNode->getMessage() )); } diff --git a/lib/Indexer/Model/IndexJob.php b/lib/Indexer/Model/IndexJob.php index 9aa2f5e27d..c96519c5f4 100644 --- a/lib/Indexer/Model/IndexJob.php +++ b/lib/Indexer/Model/IndexJob.php @@ -2,8 +2,12 @@ namespace Phpactor\Indexer\Model; +use FilesystemIterator; use Generator; +use Phar; +use PharFileInfo; use Phpactor\TextDocument\TextDocumentBuilder; +use RecursiveIteratorIterator; use SplFileInfo; class IndexJob @@ -19,6 +23,13 @@ public function generator(): Generator { foreach ($this->fileList as $fileInfo) { assert($fileInfo instanceof SplFileInfo); + + if ($fileInfo->getExtension() === 'phar') { + $phar = new Phar($fileInfo->getPathname(), FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME); + yield from $this->indexPharFile($phar); + continue; + } + $contents = @file_get_contents($fileInfo->getPathname()); if (false === $contents) { @@ -42,4 +53,25 @@ public function size(): int { return $this->fileList->count(); } + /** + * @return Generator + */ + private function indexPharFile(Phar $phar): Generator + { + $iterator = new RecursiveIteratorIterator($phar); + /** @var PharFileInfo $file */ + foreach ($iterator as $file) { + if (!$file->isFile()) { + continue; + } + if ($file->getExtension() !== 'php') { + continue; + } + + $this->indexBuilder->index( + TextDocumentBuilder::fromUri($file->getPathname())->build() + ); + yield $file->getPathname(); + } + } } diff --git a/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php b/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php index b9c0823823..d8f26e74fe 100644 --- a/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php +++ b/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php @@ -3,6 +3,7 @@ namespace Phpactor\Indexer\Model\SearchIndex; use Generator; +use PHPStan\Rules\UnusedFunctionParametersCheck; use Phpactor\Indexer\Model\IndexAccess; use Phpactor\Indexer\Model\Query\Criteria; use Phpactor\Indexer\Model\Record; @@ -23,6 +24,7 @@ public function __construct( public function search(Criteria $criteria): Generator { foreach ($this->innerIndex->search($criteria) as $result) { + if (!$this->index->has($result)) { $this->innerIndex->remove($result); diff --git a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php index 9c2c8f4e6f..480332aa5b 100644 --- a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php +++ b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php @@ -2,6 +2,7 @@ namespace Phpactor\Indexer\Tests\Unit\Adapter\Worse; +use PHPStan\Command\AnalyseApplication; use PHPUnit\Framework\TestCase; use Phpactor\Indexer\Model\Record\ClassRecord; use Phpactor\TextDocument\ByteOffset; @@ -52,6 +53,21 @@ public function testReturnsSourceCode(): void $this->assertEquals(Path::canonicalize(__FILE__), $sourceCode->uri()?->path()); } + public function testForPhar(): void + { + $record = ClassRecord::fromName('Foobar') + ->setType('class') + ->setStart(ByteOffset::fromInt(0)) + ->setEnd(ByteOffset::fromInt(10)) + ->setFilePath('phar://' . __FILE__); + + $index = new InMemoryIndex(); + $index->write($record); + $locator = $this->createLocator($index); + $sourceCode = $locator->locate(Name::fromString('Foobar')); + $this->assertEquals(Path::canonicalize(__FILE__), $sourceCode->uri()?->path()); + } + private function createLocator(InMemoryIndex $index): IndexerClassSourceLocator { return new IndexerClassSourceLocator($index); diff --git a/lib/TextDocument/Exception/TextDocumentNotFound.php b/lib/TextDocument/Exception/TextDocumentNotFound.php index a9e02fa4a1..aa33108113 100644 --- a/lib/TextDocument/Exception/TextDocumentNotFound.php +++ b/lib/TextDocument/Exception/TextDocumentNotFound.php @@ -12,6 +12,6 @@ public static function fromUri(TextDocumentUri $uri): self return new self(sprintf( 'Text document "%s" not found', $uri - )); + ).(new \Exception())->getTraceAsString()); } } diff --git a/lib/TextDocument/FilesystemTextDocumentLocator.php b/lib/TextDocument/FilesystemTextDocumentLocator.php index 5788b40cf6..17cae529a9 100644 --- a/lib/TextDocument/FilesystemTextDocumentLocator.php +++ b/lib/TextDocument/FilesystemTextDocumentLocator.php @@ -8,7 +8,7 @@ class FilesystemTextDocumentLocator implements TextDocumentLocator { public function get(TextDocumentUri $uri): TextDocument { - if (!file_exists($uri->path())) { + if (!file_exists($uri->__toString())) { throw TextDocumentNotFound::fromUri($uri); } diff --git a/lib/TextDocument/Tests/Unit/TextDocumentUriTest.php b/lib/TextDocument/Tests/Unit/TextDocumentUriTest.php index dc35de1a5a..102e3022f1 100644 --- a/lib/TextDocument/Tests/Unit/TextDocumentUriTest.php +++ b/lib/TextDocument/Tests/Unit/TextDocumentUriTest.php @@ -17,6 +17,12 @@ public function testCreate(): void $this->assertEquals('file:///C:/foo/bar.php', (string) $uri); } + public function testFromPhar(): void + { + $uri = TextDocumentUri::fromString('phar:///home/daniel/www/phpactor/phpactor/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php'); + $this->assertEquals('phar:///home/daniel/www/phpactor/phpactor/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php', (string) $uri); + } + public function testExceptionOnInvalidFormatUnix(): void { $this->expectException(InvalidUriException::class); diff --git a/lib/WorseReferenceFinder/WorseReflectionDefinitionLocator.php b/lib/WorseReferenceFinder/WorseReflectionDefinitionLocator.php index 4133955e2e..70c1b92623 100644 --- a/lib/WorseReferenceFinder/WorseReflectionDefinitionLocator.php +++ b/lib/WorseReferenceFinder/WorseReflectionDefinitionLocator.php @@ -97,7 +97,7 @@ private function gotoClass(NodeContext $nodeContext): TypeLocations throw new CouldNotLocateDefinition($e->getMessage(), 0, $e); } - $path = $class->sourceCode()->uri()?->path(); + $path = $class->sourceCode()->uri(); if (null === $path) { throw new CouldNotLocateDefinition(sprintf( From 866a0056f3fb1f86db080af9380baa6752eb2e80 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:25:44 +0000 Subject: [PATCH 02/10] Increment version to force indexing of scheme --- lib/Indexer/Adapter/Php/Serialized/FileRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Indexer/Adapter/Php/Serialized/FileRepository.php b/lib/Indexer/Adapter/Php/Serialized/FileRepository.php index c489c627e9..1c33596040 100644 --- a/lib/Indexer/Adapter/Php/Serialized/FileRepository.php +++ b/lib/Indexer/Adapter/Php/Serialized/FileRepository.php @@ -17,7 +17,7 @@ class FileRepository /** * Increment this number each time there is a B/C break in the index. */ - private const VERSION = 1; + private const VERSION = 2; /** * Flush to the filesystem after BATCH_SIZE updates From 1b0918ee8c4b0b7697d32e8fe666fe4f91b7849b Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:37:33 +0000 Subject: [PATCH 03/10] Remove remaing references --- .../Indexer/AbstractClassLikeIndexer.php | 8 +++++++- .../Tolerant/Indexer/ClassDeclarationIndexer.php | 2 +- .../Indexer/ClassLikeReferenceIndexer.php | 6 +++--- .../Indexer/ConstantDeclarationIndexer.php | 2 +- .../Tolerant/Indexer/EnumDeclarationIndexer.php | 2 +- .../Indexer/FunctionDeclarationIndexer.php | 2 +- .../Indexer/FunctionReferenceIndexer.php | 2 +- .../Indexer/InterfaceDeclarationIndexer.php | 2 +- .../Adapter/Tolerant/Indexer/MemberIndexer.php | 6 +++--- .../Tolerant/Indexer/TraitDeclarationIndexer.php | 2 +- .../Adapter/Tolerant/TolerantIndexBuilder.php | 1 - .../Model/SearchIndex/ValidatingSearchIndex.php | 1 - .../Tests/Adapter/IndexBuilderTestCase.php | 12 ++++++------ .../Indexer/ClassLikeDeclarationIndexerTest.php | 9 +++++++-- .../Worse/IndexerClassSourceLocatorTest.php | 16 ---------------- .../Exception/TextDocumentNotFound.php | 3 ++- 16 files changed, 35 insertions(+), 41 deletions(-) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php index 68e26ff1a2..8e5fd68d3e 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php @@ -68,7 +68,13 @@ protected function getClassLikeRecord(string $type, Node $node, Index $index, Te if (empty($name)) { throw new CannotIndexNode(sprintf( 'Name is empty for file "%s"', - $document->uri()->__toString() + $document->uri()?->__toString() ?? 'unknown', + )); + } + if (!$document->uri()) { + throw new CannotIndexNode(sprintf( + 'Document has no URI for class "%s"', + $name )); } diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ClassDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ClassDeclarationIndexer.php index 7e3d603c82..4263522819 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ClassDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ClassDeclarationIndexer.php @@ -25,7 +25,7 @@ public function index(Index $index, TextDocument $document, Node $node): void if ($node->name instanceof MissingToken) { throw new CannotIndexNode(sprintf( 'Class name is missing (maybe a reserved word) in: %s', - $document->uri()?->path() ?? '?', + $document->uri()?->__toString() ?? '?', )); } $record = $this->getClassLikeRecord(ClassRecord::TYPE_CLASS, $node, $index, $document); diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php index 4cac2f834b..9d4bf65a85 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php @@ -31,7 +31,7 @@ public function canIndex(Node $node): bool public function beforeParse(Index $index, TextDocument $document): void { - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uri()->__toString())); assert($fileRecord instanceof FileRecord); foreach ($fileRecord->references() as $outgoingReference) { @@ -67,11 +67,11 @@ public function index(Index $index, TextDocument $document, Node $node): void $targetRecord = $index->get(ClassRecord::fromName($name)); assert($targetRecord instanceof ClassRecord); - $targetRecord->addReference($document->uri()->path()); + $targetRecord->addReference($document->uri()->__toString()); $index->write($targetRecord); - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uri()->__toString())); assert($fileRecord instanceof FileRecord); $reference = new RecordReference( ClassRecord::RECORD_TYPE, diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php index 9465dc087f..24b8c07cd8 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php @@ -69,7 +69,7 @@ private function fromConstDeclaration(Node $node, Index $index, TextDocument $do assert($record instanceof ConstantRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uri()->path()); + $record->setFilePath($document->uriOrThrow()->__toString()); $index->write($record); } } diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/EnumDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/EnumDeclarationIndexer.php index 6d62aa2169..db0e7ac22c 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/EnumDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/EnumDeclarationIndexer.php @@ -23,7 +23,7 @@ public function index(Index $index, TextDocument $document, Node $node): void if ($node->name instanceof MissingToken) { throw new CannotIndexNode(sprintf( 'Class name is missing (maybe a reserved word) in: %s', - $document->uri()?->path() ?? '?', + $document->uri()?->__toString() ?? '?', )); } $record = $this->getClassLikeRecord(ClassRecord::TYPE_ENUM, $node, $index, $document); diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php index 26ec6f8f0b..45374a0eea 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php @@ -24,7 +24,7 @@ public function index(Index $index, TextDocument $document, Node $node): void assert($record instanceof FunctionRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uri()->path()); + $record->setFilePath($document->uriOrThrow()->__toString()); $index->write($record); } diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php index e53c0063e4..9f58966d7e 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php @@ -20,7 +20,7 @@ public function canIndex(Node $node): bool public function beforeParse(Index $index, TextDocument $document): void { - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); foreach ($fileRecord->references() as $outgoingReference) { diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/InterfaceDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/InterfaceDeclarationIndexer.php index b41542c6d7..2209e7e214 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/InterfaceDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/InterfaceDeclarationIndexer.php @@ -23,7 +23,7 @@ public function index(Index $index, TextDocument $document, Node $node): void if ($node->name instanceof MissingToken) { throw new CannotIndexNode(sprintf( 'Class name is missing (maybe a reserved word) in: %s', - $document->uri()?->path() ?? '?', + $document->uri()?->__toString() ?? '?', )); } $record = $this->getClassLikeRecord(ClassRecord::TYPE_INTERFACE, $node, $index, $document); diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/MemberIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/MemberIndexer.php index d44dd7159b..20ea9cc97a 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/MemberIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/MemberIndexer.php @@ -27,7 +27,7 @@ public function canIndex(Node $node): bool public function beforeParse(Index $index, TextDocument $document): void { - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); foreach ($fileRecord->references() as $outgoingReference) { @@ -181,10 +181,10 @@ private function writeIndex( ): void { $record = $index->get(MemberRecord::fromMemberReference(MemberReference::create($memberType, $containerFqn, $memberName))); assert($record instanceof MemberRecord); - $record->addReference($document->uri()->path()); + $record->addReference($document->uriOrThrow()->__toString()); $index->write($record); - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); $fileRecord->addReference( RecordReference::fromRecordAndOffsetAndContainerType($record, $offsetStart, $offsetEnd, $containerFqn) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/TraitDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/TraitDeclarationIndexer.php index 92ae2af41e..5c144afec4 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/TraitDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/TraitDeclarationIndexer.php @@ -23,7 +23,7 @@ public function index(Index $index, TextDocument $document, Node $node): void if ($node->name instanceof MissingToken) { throw new CannotIndexNode(sprintf( 'Class name is missing (maybe a reserved word) in: %s', - $document->uri()?->path() ?? '?', + $document->uri()?->__toString() ?? '?', )); } $record = $this->getClassLikeRecord(ClassRecord::TYPE_TRAIT, $node, $index, $document); diff --git a/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php b/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php index 086bebb1cd..401445a41c 100644 --- a/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php +++ b/lib/Indexer/Adapter/Tolerant/TolerantIndexBuilder.php @@ -18,7 +18,6 @@ use Phpactor\Indexer\Model\Index; use Phpactor\Indexer\Model\IndexBuilder; use Phpactor\TextDocument\TextDocument; -use Phpactor\WorseReflection\Core\Util\NodeUtil; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; diff --git a/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php b/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php index d8f26e74fe..55a8a092e1 100644 --- a/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php +++ b/lib/Indexer/Model/SearchIndex/ValidatingSearchIndex.php @@ -3,7 +3,6 @@ namespace Phpactor\Indexer\Model\SearchIndex; use Generator; -use PHPStan\Rules\UnusedFunctionParametersCheck; use Phpactor\Indexer\Model\IndexAccess; use Phpactor\Indexer\Model\Query\Criteria; use Phpactor\Indexer\Model\Record; diff --git a/lib/Indexer/Tests/Adapter/IndexBuilderTestCase.php b/lib/Indexer/Tests/Adapter/IndexBuilderTestCase.php index ac3e0d1de3..3a812c3d89 100644 --- a/lib/Indexer/Tests/Adapter/IndexBuilderTestCase.php +++ b/lib/Indexer/Tests/Adapter/IndexBuilderTestCase.php @@ -49,7 +49,7 @@ class IamAnAttribute {} 'IamAnAttribute', function (ClassRecord $record): void { self::assertInstanceOf(ClassRecord::class, $record); - self::assertEquals($this->workspace()->path('project/attribute.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/attribute.php'), $record->filePath()); self::assertEquals('IamAnAttribute', $record->fqn()); self::assertEquals(38, $record->start()->toInt()); self::assertEquals(52, $record->end()->toInt()); @@ -62,7 +62,7 @@ function (ClassRecord $record): void { 'ThisClass', function (ClassRecord $record): void { self::assertInstanceOf(ClassRecord::class, $record); - self::assertEquals($this->workspace()->path('project/test.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/test.php'), $record->filePath()); self::assertEquals('ThisClass', $record->fqn()); self::assertEquals(12, $record->start()->toInt()); self::assertEquals(21, $record->end()->toInt()); @@ -163,7 +163,7 @@ function (ClassRecord $record): void { 'ThisInterface', function (ClassRecord $record): void { self::assertInstanceOf(ClassRecord::class, $record); - self::assertEquals($this->workspace()->path('project/test.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/test.php'), $record->filePath()); self::assertEquals('ThisInterface', $record->fqn()); self::assertEquals(16, $record->start()->toInt()); self::assertEquals(29, $record->end()->toInt()); @@ -184,7 +184,7 @@ function (ClassRecord $record): void { 'ThisTrait', function (ClassRecord $record): void { self::assertInstanceOf(ClassRecord::class, $record); - self::assertEquals($this->workspace()->path('project/test.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/test.php'), $record->filePath()); self::assertEquals('ThisTrait', $record->fqn()); self::assertEquals(12, $record->start()->toInt()); self::assertEquals(21, $record->end()->toInt()); @@ -221,7 +221,7 @@ function (ClassRecord $record): void { 'SomeEnum', function (ClassRecord $record): void { self::assertInstanceOf(ClassRecord::class, $record); - self::assertEquals($this->workspace()->path('project/test.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/test.php'), $record->filePath()); self::assertEquals('SomeEnum', $record->fqn()); self::assertEquals(11, $record->start()->toInt()); self::assertEquals(19, $record->end()->toInt()); @@ -433,7 +433,7 @@ function foobar() {}; , 'Barfoos\foobar', function (FunctionRecord $record): void { self::assertCount(1, $record->references()); - self::assertEquals($this->workspace()->path('project/test1.php'), $record->filePath()); + self::assertEquals('file://' . $this->workspace()->path('project/test1.php'), $record->filePath()); } ]; } diff --git a/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php b/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php index 132123924a..f54b0a7ad4 100644 --- a/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php +++ b/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php @@ -122,13 +122,13 @@ public function provideSearch(): Generator yield 'exact match' => [ "// File: src/file1.php\nsetFilePath($this->workspace()->path('src/file1.php'))] + [ClassRecord::fromName('Barfoo')->setFilePath($this->workspacePath('src/file1.php'))] ]; yield 'namespaced match' => [ "// File: src/file1.php\nsetFilePath($this->workspace()->path('src/file1.php'))] + [ClassRecord::fromName('Bar\Barfoo')->setFilePath($this->workspacePath('src/file1.php'))] ]; yield 'gh-2098: does not index reserved class name' => [ @@ -169,4 +169,9 @@ public function provideInvalidClasses(): Generator 'Class name is missing', ]; } + + private function workspacePath(string $path): string + { + return 'file://'.$this->workspace()->path($path); + } } diff --git a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php index 480332aa5b..9c2c8f4e6f 100644 --- a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php +++ b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php @@ -2,7 +2,6 @@ namespace Phpactor\Indexer\Tests\Unit\Adapter\Worse; -use PHPStan\Command\AnalyseApplication; use PHPUnit\Framework\TestCase; use Phpactor\Indexer\Model\Record\ClassRecord; use Phpactor\TextDocument\ByteOffset; @@ -53,21 +52,6 @@ public function testReturnsSourceCode(): void $this->assertEquals(Path::canonicalize(__FILE__), $sourceCode->uri()?->path()); } - public function testForPhar(): void - { - $record = ClassRecord::fromName('Foobar') - ->setType('class') - ->setStart(ByteOffset::fromInt(0)) - ->setEnd(ByteOffset::fromInt(10)) - ->setFilePath('phar://' . __FILE__); - - $index = new InMemoryIndex(); - $index->write($record); - $locator = $this->createLocator($index); - $sourceCode = $locator->locate(Name::fromString('Foobar')); - $this->assertEquals(Path::canonicalize(__FILE__), $sourceCode->uri()?->path()); - } - private function createLocator(InMemoryIndex $index): IndexerClassSourceLocator { return new IndexerClassSourceLocator($index); diff --git a/lib/TextDocument/Exception/TextDocumentNotFound.php b/lib/TextDocument/Exception/TextDocumentNotFound.php index aa33108113..8385adbba9 100644 --- a/lib/TextDocument/Exception/TextDocumentNotFound.php +++ b/lib/TextDocument/Exception/TextDocumentNotFound.php @@ -4,6 +4,7 @@ use Phpactor\TextDocument\TextDocumentUri; use RuntimeException; +use Exception; final class TextDocumentNotFound extends RuntimeException { @@ -12,6 +13,6 @@ public static function fromUri(TextDocumentUri $uri): self return new self(sprintf( 'Text document "%s" not found', $uri - ).(new \Exception())->getTraceAsString()); + ).(new Exception())->getTraceAsString()); } } From 392429ac84913ebc0c014201208b9ca4b66596a3 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:40:37 +0000 Subject: [PATCH 04/10] Fix other places --- .../Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php | 2 +- .../Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php | 2 +- lib/Indexer/Model/Record/HasPathTrait.php | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php index 24b8c07cd8..a0ae293d7e 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php @@ -95,7 +95,7 @@ private function fromDefine(CallExpression $node, Index $index, TextDocument $do assert($record instanceof ConstantRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uri()->path()); + $record->setFilePath($document->uriOrThrow()->__toString()); $index->write($record); // Return after the first argument, because we only need the name of the constant. diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php index 9f58966d7e..9df32663ba 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php @@ -51,7 +51,7 @@ public function index(Index $index, TextDocument $document, Node $node): void $targetRecord->addReference($document->uri()->path()); $index->write($targetRecord); - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->path())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); $fileRecord->addReference( diff --git a/lib/Indexer/Model/Record/HasPathTrait.php b/lib/Indexer/Model/Record/HasPathTrait.php index 02c84838bb..3e58ed66da 100644 --- a/lib/Indexer/Model/Record/HasPathTrait.php +++ b/lib/Indexer/Model/Record/HasPathTrait.php @@ -2,12 +2,17 @@ namespace Phpactor\Indexer\Model\Record; +use RuntimeException; + trait HasPathTrait { protected ?string $filePath = null; public function setFilePath(string $filePath): self { + if (!strpos($filePath, ':/')) { + throw new RuntimeException('Invalid file path: '.$filePath); + } $this->filePath = $filePath; return $this; } From 1382cba087d377061b1bfda73fcef7fbdf9c129a Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:55:49 +0000 Subject: [PATCH 05/10] Use typed URI for indexing --- .../Tolerant/Indexer/AbstractClassLikeIndexer.php | 2 +- .../Tolerant/Indexer/ConstantDeclarationIndexer.php | 4 ++-- .../Tolerant/Indexer/FunctionDeclarationIndexer.php | 2 +- .../Tolerant/Indexer/FunctionReferenceIndexer.php | 2 +- .../Query/Criteria/FileAbsolutePathBeginsWith.php | 9 ++++++++- lib/Indexer/Model/Record/FileRecord.php | 4 ++-- lib/Indexer/Model/Record/HasPath.php | 7 ++++++- lib/Indexer/Model/Record/HasPathTrait.php | 9 +++------ .../Indexer/ClassLikeDeclarationIndexerTest.php | 5 +++-- .../Adapter/Worse/IndexerClassSourceLocatorTest.php | 5 +++-- .../Adapter/Worse/IndexerFunctionSourceLocatorTest.php | 5 +++-- .../Query/Criteria/FileAbsolutePathBeginsWithTest.php | 7 ++++--- .../Model/SearchIndex/ValidatingSearchIndexTest.php | 10 ++++++++-- 13 files changed, 45 insertions(+), 26 deletions(-) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php index 8e5fd68d3e..6b9ed93990 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/AbstractClassLikeIndexer.php @@ -83,7 +83,7 @@ protected function getClassLikeRecord(string $type, Node $node, Index $index, Te /** @var ClassDeclaration|InterfaceDeclaration|EnumDeclaration|TraitDeclaration $node */ $record->setStart(ByteOffset::fromInt($node->name->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->name->getEndPosition())); - $record->setFilePath($document->uri()->__toString()); + $record->setFilePath($document->uriOrThrow()); $record->setType($type); return $record; diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php index a0ae293d7e..42c86db6a5 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ConstantDeclarationIndexer.php @@ -69,7 +69,7 @@ private function fromConstDeclaration(Node $node, Index $index, TextDocument $do assert($record instanceof ConstantRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uriOrThrow()->__toString()); + $record->setFilePath($document->uriOrThrow()); $index->write($record); } } @@ -95,7 +95,7 @@ private function fromDefine(CallExpression $node, Index $index, TextDocument $do assert($record instanceof ConstantRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uriOrThrow()->__toString()); + $record->setFilePath($document->uriOrThrow()); $index->write($record); // Return after the first argument, because we only need the name of the constant. diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php index 45374a0eea..f5ddbceb1a 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionDeclarationIndexer.php @@ -24,7 +24,7 @@ public function index(Index $index, TextDocument $document, Node $node): void assert($record instanceof FunctionRecord); $record->setStart(ByteOffset::fromInt($node->getStartPosition())); $record->setEnd(ByteOffset::fromInt($node->getEndPosition())); - $record->setFilePath($document->uriOrThrow()->__toString()); + $record->setFilePath($document->uriOrThrow()); $index->write($record); } diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php index 9df32663ba..ca67efd695 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/FunctionReferenceIndexer.php @@ -48,7 +48,7 @@ public function index(Index $index, TextDocument $document, Node $node): void $targetRecord = $index->get(FunctionRecord::fromName($name)); assert($targetRecord instanceof FunctionRecord); - $targetRecord->addReference($document->uri()->path()); + $targetRecord->addReference($document->uriOrThrow()); $index->write($targetRecord); $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); diff --git a/lib/Indexer/Model/Query/Criteria/FileAbsolutePathBeginsWith.php b/lib/Indexer/Model/Query/Criteria/FileAbsolutePathBeginsWith.php index 5c9fb84953..6bea429661 100644 --- a/lib/Indexer/Model/Query/Criteria/FileAbsolutePathBeginsWith.php +++ b/lib/Indexer/Model/Query/Criteria/FileAbsolutePathBeginsWith.php @@ -18,7 +18,14 @@ public function isSatisfiedBy(Record $record): bool if (!$record instanceof HasPath) { return false; } + $path = $record->filePath(); + if (!$path) { + return false; + } + if ($pos = strpos($path, ':///')) { + $path = substr($path, $pos + 3); + } - return str_starts_with($record->filePath() ?? '', $this->prefix); + return str_starts_with($path, $this->prefix); } } diff --git a/lib/Indexer/Model/Record/FileRecord.php b/lib/Indexer/Model/Record/FileRecord.php index 48403533e8..1ce8fdc4d0 100644 --- a/lib/Indexer/Model/Record/FileRecord.php +++ b/lib/Indexer/Model/Record/FileRecord.php @@ -18,9 +18,9 @@ class FileRecord implements HasPath, Record */ private array $references = []; - public function __construct(string $filePath) + private function __construct(string $filePath) { - $this->setFilePath($filePath); + $this->filePath = $filePath; } public function __wakeup(): void diff --git a/lib/Indexer/Model/Record/HasPath.php b/lib/Indexer/Model/Record/HasPath.php index c3fdeb5d7d..2610178713 100644 --- a/lib/Indexer/Model/Record/HasPath.php +++ b/lib/Indexer/Model/Record/HasPath.php @@ -2,12 +2,17 @@ namespace Phpactor\Indexer\Model\Record; +use Phpactor\TextDocument\TextDocumentUri; + interface HasPath { /** * @return $this */ - public function setFilePath(string $filePath); + public function setFilePath(TextDocumentUri $filePath); + /** + * Rename to URI + */ public function filePath(): ?string; } diff --git a/lib/Indexer/Model/Record/HasPathTrait.php b/lib/Indexer/Model/Record/HasPathTrait.php index 3e58ed66da..ae6d27bdb3 100644 --- a/lib/Indexer/Model/Record/HasPathTrait.php +++ b/lib/Indexer/Model/Record/HasPathTrait.php @@ -2,18 +2,15 @@ namespace Phpactor\Indexer\Model\Record; -use RuntimeException; +use Phpactor\TextDocument\TextDocumentUri; trait HasPathTrait { protected ?string $filePath = null; - public function setFilePath(string $filePath): self + public function setFilePath(TextDocumentUri $uri): self { - if (!strpos($filePath, ':/')) { - throw new RuntimeException('Invalid file path: '.$filePath); - } - $this->filePath = $filePath; + $this->filePath = $uri->__toString(); return $this; } diff --git a/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php b/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php index f54b0a7ad4..c448602dbe 100644 --- a/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php +++ b/lib/Indexer/Tests/Adapter/Tolerant/Indexer/ClassLikeDeclarationIndexerTest.php @@ -10,6 +10,7 @@ use Phpactor\Indexer\Model\Query\Criteria\ShortNameBeginsWith; use Phpactor\Indexer\Model\Record\ClassRecord; use Phpactor\Indexer\Tests\Adapter\Tolerant\TolerantIndexerTestCase; +use Phpactor\TextDocument\TextDocumentUri; use Prophecy\Argument; use Psr\Log\LoggerInterface; use RuntimeException; @@ -170,8 +171,8 @@ public function provideInvalidClasses(): Generator ]; } - private function workspacePath(string $path): string + private function workspacePath(string $path): TextDocumentUri { - return 'file://'.$this->workspace()->path($path); + return TextDocumentUri::fromString($this->workspace()->path($path)); } } diff --git a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php index 9c2c8f4e6f..3e4facf61e 100644 --- a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php +++ b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerClassSourceLocatorTest.php @@ -7,6 +7,7 @@ use Phpactor\TextDocument\ByteOffset; use Phpactor\Indexer\Adapter\Php\InMemory\InMemoryIndex; use Phpactor\Indexer\Adapter\Worse\IndexerClassSourceLocator; +use Phpactor\TextDocument\TextDocumentUri; use Phpactor\WorseReflection\Core\Exception\SourceNotFound; use Phpactor\WorseReflection\Core\Name; use Symfony\Component\Filesystem\Path; @@ -29,7 +30,7 @@ public function testThrowsExceptionIfFileDoesNotExist(): void ->setType('class') ->setStart(ByteOffset::fromInt(0)) ->setEnd(ByteOffset::fromInt(0)) - ->setFilePath('nope.php'); + ->setFilePath(TextDocumentUri::fromString('/nope.php')); $index = new InMemoryIndex(); $index->write($record); @@ -43,7 +44,7 @@ public function testReturnsSourceCode(): void ->setType('class') ->setStart(ByteOffset::fromInt(0)) ->setEnd(ByteOffset::fromInt(10)) - ->setFilePath(__FILE__); + ->setFilePath(TextDocumentUri::fromString(__FILE__)); $index = new InMemoryIndex(); $index->write($record); diff --git a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerFunctionSourceLocatorTest.php b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerFunctionSourceLocatorTest.php index 797babeb34..cb55ab6ced 100644 --- a/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerFunctionSourceLocatorTest.php +++ b/lib/Indexer/Tests/Unit/Adapter/Worse/IndexerFunctionSourceLocatorTest.php @@ -7,6 +7,7 @@ use Phpactor\Indexer\Adapter\Php\InMemory\InMemoryIndex; use Phpactor\Indexer\Adapter\Worse\IndexerFunctionSourceLocator; use Phpactor\Indexer\Model\Record\FunctionRecord; +use Phpactor\TextDocument\TextDocumentUri; use Symfony\Component\Filesystem\Path; use Phpactor\WorseReflection\Core\Exception\SourceNotFound; use Phpactor\WorseReflection\Core\Name; @@ -28,7 +29,7 @@ public function testThrowsExceptionIfFileDoesNotExist(): void $record = new FunctionRecord( FullyQualifiedName::fromString('Foobar') ); - $record->setFilePath('nope.php'); + $record->setFilePath(TextDocumentUri::fromString('/nope.php')); $index = new InMemoryIndex(); $index->write($record); $locator = $this->createLocator($index); @@ -40,7 +41,7 @@ public function testReturnsSourceCode(): void $record = new FunctionRecord( FullyQualifiedName::fromString('Foobar') ); - $record->setFilePath(__FILE__); + $record->setFilePath(TextDocumentUri::fromString(__FILE__)); $index = new InMemoryIndex(); $index->write($record); $locator = $this->createLocator($index); diff --git a/lib/Indexer/Tests/Unit/Model/Query/Criteria/FileAbsolutePathBeginsWithTest.php b/lib/Indexer/Tests/Unit/Model/Query/Criteria/FileAbsolutePathBeginsWithTest.php index 60e598c6eb..463e7c83e0 100644 --- a/lib/Indexer/Tests/Unit/Model/Query/Criteria/FileAbsolutePathBeginsWithTest.php +++ b/lib/Indexer/Tests/Unit/Model/Query/Criteria/FileAbsolutePathBeginsWithTest.php @@ -5,25 +5,26 @@ use PHPUnit\Framework\TestCase; use Phpactor\Indexer\Model\Query\Criteria; use Phpactor\Indexer\Model\Record\ClassRecord; +use Phpactor\TextDocument\TextDocumentUri; class FileAbsolutePathBeginsWithTest extends TestCase { public function testDoesNotBeginWith(): void { - $record = ClassRecord::fromName('Foobar\\Barfoo')->setFilePath('/foobar'); + $record = ClassRecord::fromName('Foobar\\Barfoo')->setFilePath(TextDocumentUri::fromString('/foobar')); self::assertFalse(Criteria::fileAbsolutePathBeginsWith('/baz')->isSatisfiedBy($record)); } public function testBeginsWith(): void { - $record = ClassRecord::fromName('Foobar\\Barfoo')->setFilePath('/foobar/bazboo/baz.php'); + $record = ClassRecord::fromName('Foobar\\Barfoo')->setFilePath(TextDocumentUri::fromString('/foobar/bazboo/baz.php')); self::assertTrue(Criteria::fileAbsolutePathBeginsWith('/foobar')->isSatisfiedBy($record)); } public function testBeginsWithTrailingSlash(): void { $record = ClassRecord::fromName('Foobar\\Barfoo') - ->setFilePath('/foobar/bazboo/baz.php'); + ->setFilePath(TextDocumentUri::fromString('/foobar/bazboo/baz.php')); self::assertTrue( Criteria::fileAbsolutePathBeginsWith('/foobar/')->isSatisfiedBy($record) diff --git a/lib/Indexer/Tests/Unit/Model/SearchIndex/ValidatingSearchIndexTest.php b/lib/Indexer/Tests/Unit/Model/SearchIndex/ValidatingSearchIndexTest.php index b4d9dc5790..9b826e26a9 100644 --- a/lib/Indexer/Tests/Unit/Model/SearchIndex/ValidatingSearchIndexTest.php +++ b/lib/Indexer/Tests/Unit/Model/SearchIndex/ValidatingSearchIndexTest.php @@ -10,6 +10,7 @@ use Phpactor\Indexer\Model\Record\MemberRecord; use Phpactor\Indexer\Model\SearchIndex\ValidatingSearchIndex; use Phpactor\Indexer\Tests\IntegrationTestCase; +use Phpactor\TextDocument\TextDocumentUri; use Psr\Log\NullLogger; class ValidatingSearchIndexTest extends IntegrationTestCase @@ -52,7 +53,7 @@ public function testYieldsRecordsWithoutAPath(): void public function testRemovesFromIndexIfFileDoesNotExist(): void { $record = ClassRecord::fromName('Foobar') - ->setFilePath($this->workspace()->path('nope.php')); + ->setFilePath($this->workspacePath('nope.php')); $this->index->write($record); $this->innerSearchIndex->write($record); @@ -65,7 +66,7 @@ public function testYieldsSearchResultIfFileExists(): void { $this->workspace()->put('yep.php', 'foo'); $record = ClassRecord::fromName('Foobar') - ->setFilePath($this->workspace()->path('yep.php')); + ->setFilePath($this->workspacePath('yep.php')); $this->index->write($record); $this->innerSearchIndex->write($record); @@ -78,4 +79,9 @@ private static function assertSearchCount(int $int, Generator $generator): void { self::assertEquals($int, count(iterator_to_array($generator))); } + + private function workspacePath(string $string): TextDocumentUri + { + return TextDocumentUri::fromString($this->workspace()->path($string)); + } } From 3eedacebe6bd960a4dc7e63b1cc62a34956ffa34 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 14:59:14 +0000 Subject: [PATCH 06/10] Fixes --- .../Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php | 6 +++--- lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php b/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php index 9d4bf65a85..f71c3d3d66 100644 --- a/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php +++ b/lib/Indexer/Adapter/Tolerant/Indexer/ClassLikeReferenceIndexer.php @@ -31,7 +31,7 @@ public function canIndex(Node $node): bool public function beforeParse(Index $index, TextDocument $document): void { - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->__toString())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); foreach ($fileRecord->references() as $outgoingReference) { @@ -67,11 +67,11 @@ public function index(Index $index, TextDocument $document, Node $node): void $targetRecord = $index->get(ClassRecord::fromName($name)); assert($targetRecord instanceof ClassRecord); - $targetRecord->addReference($document->uri()->__toString()); + $targetRecord->addReference($document->uriOrThrow()->__toString()); $index->write($targetRecord); - $fileRecord = $index->get(FileRecord::fromPath($document->uri()->__toString())); + $fileRecord = $index->get(FileRecord::fromPath($document->uriOrThrow()->__toString())); assert($fileRecord instanceof FileRecord); $reference = new RecordReference( ClassRecord::RECORD_TYPE, diff --git a/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php b/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php index 7950d14b2f..eedd087f77 100644 --- a/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php +++ b/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php @@ -37,10 +37,10 @@ public function testRenameReferencesInTwoFiles(): void $renamer = $this->createRenamer([$document1, $document2, $document3, $document4], [ (new ClassRecord('One'))->setType('class')->addReference($this->path('3.php'))->addReference($this->path('4.php')), - (new FileRecord($this->path('3.php')))->addReference( + FileRecord::fromPath('file://' . $this->path('3.php'))->addReference( new RecordReference(ClassRecord::RECORD_TYPE, 'One', 10, end: 20) ), - (new FileRecord($this->path('4.php')))->addReference( + FileRecord::fromPath('file://' . $this->path('4.php'))->addReference( new RecordReference(ClassRecord::RECORD_TYPE, 'One', 10, end: 20) ) ]); From ac69933c1f4b69a105897b744728c1e169638802 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 15:04:51 +0000 Subject: [PATCH 07/10] Fixes --- CHANGELOG.md | 4 ++++ lib/Rename/Adapter/ClassMover/FileRenamer.php | 4 ++-- lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7900890856..929a5d68b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Changelog ## master +Features: + + - PHAR Indexing #2412 @dantleech + Improvements: - Basic support for `array_reduce` stub #2576 diff --git a/lib/Rename/Adapter/ClassMover/FileRenamer.php b/lib/Rename/Adapter/ClassMover/FileRenamer.php index 6a20f11999..ec59c0d296 100644 --- a/lib/Rename/Adapter/ClassMover/FileRenamer.php +++ b/lib/Rename/Adapter/ClassMover/FileRenamer.php @@ -48,11 +48,11 @@ public function renameFile(TextDocumentUri $from, TextDocumentUri $to): Promise $edits = TextEdits::none(); $seen = []; foreach ($references as $reference) { - if (isset($seen[$reference->location()->uri()->path()])) { + if (isset($seen[$reference->location()->uri()->__toString()])) { continue; } - $seen[$reference->location()->uri()->path()] = true; + $seen[$reference->location()->uri()->__toString()] = true; try { $document = $this->locator->get($reference->location()->uri()); diff --git a/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php b/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php index eedd087f77..9042f8b69f 100644 --- a/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php +++ b/lib/Rename/Tests/Adapter/ClassMover/FileRenamerTest.php @@ -36,7 +36,9 @@ public function testRenameReferencesInTwoFiles(): void $document4 = $this->createDocument('4.php', 'createRenamer([$document1, $document2, $document3, $document4], [ - (new ClassRecord('One'))->setType('class')->addReference($this->path('3.php'))->addReference($this->path('4.php')), + (new ClassRecord('One'))->setType('class')->addReference( + 'file://' . $this->path('3.php') + )->addReference('file://' . $this->path('4.php')), FileRecord::fromPath('file://' . $this->path('3.php'))->addReference( new RecordReference(ClassRecord::RECORD_TYPE, 'One', 10, end: 20) ), @@ -45,7 +47,7 @@ public function testRenameReferencesInTwoFiles(): void ) ]); - $edits = wait($renamer->renameFile($document1->uri(), $document2->uri())); + $edits = wait($renamer->renameFile($document1->uriOrThrow(), $document2->uriOrThrow())); self::assertInstanceOf(LocatedTextEditsMap::class, $edits); assert($edits instanceof LocatedTextEditsMap); From 47fad7fbcfc272414ec97ae8a080e6c68de4d692 Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 15:40:06 +0000 Subject: [PATCH 08/10] Add test for indexing phar --- .github/workflows/ci.yml | 2 +- .../Filesystem/FilesystemFileListProvider.php | 2 +- lib/Indexer/Extension/IndexerExtension.php | 2 +- lib/Indexer/IndexAgentBuilder.php | 3 +- lib/Indexer/Model/IndexJob.php | 1 + lib/Indexer/Tests/Adapter/PharIndexTest.php | 35 +++++++++++++++++++ 6 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 lib/Indexer/Tests/Adapter/PharIndexTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4b455f06c..9a57423b47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: composer-options: "--no-scripts" - name: "Run PHPUnit" - run: "php -dzend.assertions=1 vendor/bin/phpunit" + run: "php -dphar.readonly=0 -dzend.assertions=1 vendor/bin/phpunit" phpstan: name: "PHPStan (${{ matrix.php-version }})" diff --git a/lib/Indexer/Adapter/Filesystem/FilesystemFileListProvider.php b/lib/Indexer/Adapter/Filesystem/FilesystemFileListProvider.php index 40fe59c094..cc4a269701 100644 --- a/lib/Indexer/Adapter/Filesystem/FilesystemFileListProvider.php +++ b/lib/Indexer/Adapter/Filesystem/FilesystemFileListProvider.php @@ -20,7 +20,7 @@ public function __construct( private Filesystem $filesystem, private array $includePatterns = [], private array $excludePatterns = [], - private array $supportedExtensions = ['php'], + private array $supportedExtensions = ['php', 'phar'], ) { } diff --git a/lib/Indexer/Extension/IndexerExtension.php b/lib/Indexer/Extension/IndexerExtension.php index 18571d4de6..4fb1b97481 100644 --- a/lib/Indexer/Extension/IndexerExtension.php +++ b/lib/Indexer/Extension/IndexerExtension.php @@ -66,7 +66,7 @@ class IndexerExtension implements Extension public const PARAM_IMPLEMENTATIONS_DEEP_REFERENCES = 'indexer.implementation_finder.deep'; public const PARAM_STUB_PATHS = 'indexer.stub_paths'; public const PARAM_SUPPORTED_EXTENSIONS = 'indexer.supported_extensions'; - const TAG_WATCHER = 'indexer.watcher'; + public const TAG_WATCHER = 'indexer.watcher'; private const SERVICE_INDEXER_EXCLUDE_PATTERNS = 'indexer.exclude_patterns'; private const SERVICE_INDEXER_INCLUDE_PATTERNS = 'indexer.include_patterns'; private const PARAM_PROJECT_ROOT = 'indexer.project_root'; diff --git a/lib/Indexer/IndexAgentBuilder.php b/lib/Indexer/IndexAgentBuilder.php index 6bf8646f24..f19bfb7cc6 100644 --- a/lib/Indexer/IndexAgentBuilder.php +++ b/lib/Indexer/IndexAgentBuilder.php @@ -45,6 +45,7 @@ final class IndexAgentBuilder */ private array $includePatterns = [ '/**/*.php', + '/**/*.phar', ]; /** @@ -68,7 +69,7 @@ final class IndexAgentBuilder /** * @var list */ - private array $supportedExtensions = ['php']; + private array $supportedExtensions = ['php', 'phar']; private LoggerInterface $logger; diff --git a/lib/Indexer/Model/IndexJob.php b/lib/Indexer/Model/IndexJob.php index c96519c5f4..e0f8a056a2 100644 --- a/lib/Indexer/Model/IndexJob.php +++ b/lib/Indexer/Model/IndexJob.php @@ -21,6 +21,7 @@ public function __construct(private IndexBuilder $indexBuilder, private FileList */ public function generator(): Generator { + foreach ($this->fileList as $fileInfo) { assert($fileInfo instanceof SplFileInfo); diff --git a/lib/Indexer/Tests/Adapter/PharIndexTest.php b/lib/Indexer/Tests/Adapter/PharIndexTest.php new file mode 100644 index 0000000000..a665c1e9a1 --- /dev/null +++ b/lib/Indexer/Tests/Adapter/PharIndexTest.php @@ -0,0 +1,35 @@ +workspace()->reset(); + } + + public function testIndexPhar(): void + { + if (ini_get('phar.readonly') != 0) { + $this->markTestSkipped('PHAR is in readonly not set'); + } + + $this->workspace()->put('phar/index.php', 'workspace()->mkdir('repo'); + + // create phar + $phar = new Phar($this->workspace()->path('repo/index.phar'), 0, 'index.phar'); + $phar->buildFromDirectory($this->workspace()->path('phar')); + + $agent = $this->indexAgentBuilder('repo')->buildTestAgent(); + $agent->indexer()->getJob()->run(); + $hellos = iterator_to_array($agent->search()->search(Criteria::shortNameContains('Hello'))); + self::assertCount(1, $hellos); + } + +} From 5e3de977f32ea678c539074f0877281a1a45396b Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 15:41:13 +0000 Subject: [PATCH 09/10] Update --- lib/Indexer/Model/IndexJob.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Indexer/Model/IndexJob.php b/lib/Indexer/Model/IndexJob.php index e0f8a056a2..d58b39a007 100644 --- a/lib/Indexer/Model/IndexJob.php +++ b/lib/Indexer/Model/IndexJob.php @@ -25,6 +25,7 @@ public function generator(): Generator foreach ($this->fileList as $fileInfo) { assert($fileInfo instanceof SplFileInfo); + // TODO: could refactor this to iterate the PHAR in the file list provider. if ($fileInfo->getExtension() === 'phar') { $phar = new Phar($fileInfo->getPathname(), FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME); yield from $this->indexPharFile($phar); From 969fc27ed370f23f3cee9f29bc32a08b7e77a82c Mon Sep 17 00:00:00 2001 From: Daniel Leech Date: Sat, 16 Mar 2024 15:41:55 +0000 Subject: [PATCH 10/10] Add extensions to config --- doc/reference/configuration.rst | 4 ++-- lib/Indexer/Extension/IndexerExtension.php | 3 ++- lib/TextDocument/Exception/TextDocumentNotFound.php | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/reference/configuration.rst b/doc/reference/configuration.rst index a8911b5b9d..9d5317f155 100644 --- a/doc/reference/configuration.rst +++ b/doc/reference/configuration.rst @@ -1614,7 +1614,7 @@ Type: array Glob patterns to include while indexing -**Default**: ``["\/**\/*.php"]`` +**Default**: ``["\/**\/*.php","\/**\/*.phar"]`` .. _param_indexer.exclude_patterns: @@ -1758,7 +1758,7 @@ Type: array File extensions (e.g. `php`) for files that should be indexed -**Default**: ``["php"]`` +**Default**: ``["php","phar"]`` .. _ObjectRendererExtension: diff --git a/lib/Indexer/Extension/IndexerExtension.php b/lib/Indexer/Extension/IndexerExtension.php index 4fb1b97481..399155df04 100644 --- a/lib/Indexer/Extension/IndexerExtension.php +++ b/lib/Indexer/Extension/IndexerExtension.php @@ -79,6 +79,7 @@ public function configure(Resolver $schema): void self::PARAM_INDEX_PATH => '%cache%/index/%project_id%', self::PARAM_INCLUDE_PATTERNS => [ '/**/*.php', + '/**/*.phar', ], self::PARAM_EXCLUDE_PATTERNS => [ '/vendor/**/Tests/**/*', @@ -92,7 +93,7 @@ public function configure(Resolver $schema): void self::PARAM_PROJECT_ROOT => '%project_root%', self::PARAM_REFERENCES_DEEP_REFERENCES => true, self::PARAM_IMPLEMENTATIONS_DEEP_REFERENCES => true, - self::PARAM_SUPPORTED_EXTENSIONS => ['php'], + self::PARAM_SUPPORTED_EXTENSIONS => ['php', 'phar'], ]); $schema->setDescriptions([ self::PARAM_ENABLED_WATCHERS => 'List of allowed watchers. The first watcher that supports the current system will be used', diff --git a/lib/TextDocument/Exception/TextDocumentNotFound.php b/lib/TextDocument/Exception/TextDocumentNotFound.php index 8385adbba9..a9e02fa4a1 100644 --- a/lib/TextDocument/Exception/TextDocumentNotFound.php +++ b/lib/TextDocument/Exception/TextDocumentNotFound.php @@ -4,7 +4,6 @@ use Phpactor\TextDocument\TextDocumentUri; use RuntimeException; -use Exception; final class TextDocumentNotFound extends RuntimeException { @@ -13,6 +12,6 @@ public static function fromUri(TextDocumentUri $uri): self return new self(sprintf( 'Text document "%s" not found', $uri - ).(new Exception())->getTraceAsString()); + )); } }