Skip to content
This repository was archived by the owner on Mar 6, 2022. It is now read-only.

Add search by fqn-begins-with #26

Merged
merged 3 commits into from
Nov 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ $container = PhpactorContainer::fromExtensions([
IndexerExtension::PARAM_EXCLUDE_PATTERNS => ['cache'],
IndexerExtension::PARAM_ENABLED_WATCHERS => ['watchman', 'find'],
WorseReflectionExtension::PARAM_ENABLE_CACHE => true,
LoggingExtension::PARAM_ENABLED => true,
LoggingExtension::PARAM_ENABLED => false,
LoggingExtension::PARAM_LEVEL => 'debug',
LoggingExtension::PARAM_PATH=> 'php://stdout',
]);
Expand Down
71 changes: 60 additions & 11 deletions lib/Extension/Command/IndexSearchCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

namespace Phpactor\Indexer\Extension\Command;

use Phpactor\Indexer\Model\Query\Criteria\ShortNameBeginsWith;
use Phpactor\Indexer\Model\Query\Criteria;
use Phpactor\Indexer\Model\SearchClient;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class IndexSearchCommand extends Command
{
const ARG_SEARCH = 'search';
const OPT_FQN_BEGINS = 'fqn-begins';
const OPT_SHORT_NAME_BEGINS = 'short-name-begins';
const OPT_SHORT_NAME = 'short-name';
const OPT_IS_FUNCTION = 'is-function';
const OPT_IS_CLASS = 'is-class';
const OPT_IS_MEMBER = 'is-member';
const OPT_LIMIT = 'limit';


/**
* @var SearchClient
Expand All @@ -26,21 +34,62 @@ public function __construct(SearchClient $searchClient)

protected function configure(): void
{
$this->addArgument(self::ARG_SEARCH, InputArgument::REQUIRED, 'Search text');
$this->setDescription(
'Search the index'
);
$this->setDescription('Search the index');

$this->addOption(self::OPT_FQN_BEGINS, null, InputOption::VALUE_REQUIRED, 'FQN begins with');
$this->addOption(self::OPT_SHORT_NAME_BEGINS, null, InputOption::VALUE_REQUIRED, 'Short-name begins with');
$this->addOption(self::OPT_SHORT_NAME, null, InputOption::VALUE_REQUIRED, 'Exact short name');
$this->addOption(self::OPT_IS_FUNCTION, null, InputOption::VALUE_NONE, 'Functions only');
$this->addOption(self::OPT_IS_CLASS, null, InputOption::VALUE_NONE, 'Classes only');
$this->addOption(self::OPT_LIMIT, 'l', InputOption::VALUE_REQUIRED, 'Limit number of results');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$search = $input->getArgument(self::ARG_SEARCH);
assert(is_string($search));
$shortName = $input->getOption(self::OPT_SHORT_NAME);
$shortNameBegins = $input->getOption(self::OPT_SHORT_NAME_BEGINS);
$fqnBegins = $input->getOption(self::OPT_FQN_BEGINS);
$isFunction = $input->getOption(self::OPT_IS_FUNCTION);
$isClass = $input->getOption(self::OPT_IS_CLASS);
$limitRaw = $input->getOption(self::OPT_LIMIT);
$limit = is_numeric($limitRaw) ? (int)$limitRaw : null;

$criterias = [];

if ($shortName) {
/** @phpstan-ignore-next-line */
$criterias[] = Criteria::exactShortName($shortName);
}

if ($shortNameBegins) {
/** @phpstan-ignore-next-line */
$criterias[] = Criteria::shortNameBeginsWith($shortNameBegins);
}

$criteria = new ShortNameBeginsWith($search);
foreach ($this->searchClient->search($criteria) as $result) {
$output->writeln(sprintf('<comment>%s</> <fg=cyan>#</> %s', $result->recordType(), $result->identifier()));
if ($fqnBegins) {
/** @phpstan-ignore-next-line */
$criterias[] = Criteria::fqnBeginsWith($fqnBegins);
}

if ($isFunction) {
$criterias[] = Criteria::isFunction();
}

if ($isClass) {
$criterias[] = Criteria::isClass();
}

foreach ($this->searchClient->search(Criteria::and(...$criterias)) as $index => $result) {
if ($limit && $index === $limit) {
break;
}
$output->writeln(sprintf(
'<comment>%s</> <fg=cyan>#</> %s',
$result->recordType(),
$result->identifier()
));
}

return 0;
}
}
2 changes: 1 addition & 1 deletion lib/Model/MemoryUsage.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function memoryUsageFormatted(): string
return sprintf('%s/%s mb', $this->formatMemory($this->memoryUsage), $this->formatMemory($this->memoryLimit));
}

public function memoryLimit(): int
public function memoryLimit(): ?int
{
return $this->memoryLimit;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/Model/Query/Criteria.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Phpactor\Indexer\Model\Query\Criteria\AndCriteria;
use Phpactor\Indexer\Model\Query\Criteria\ExactShortName;
use Phpactor\Indexer\Model\Query\Criteria\FqnBeginsWith;
use Phpactor\Indexer\Model\Query\Criteria\IsClass;
use Phpactor\Indexer\Model\Query\Criteria\IsFunction;
use Phpactor\Indexer\Model\Query\Criteria\IsMember;
Expand All @@ -25,6 +26,11 @@ public static function shortNameBeginsWith(string $name): ShortNameBeginsWith
return new ShortNameBeginsWith($name);
}

public static function fqnBeginsWith(string $name): FqnBeginsWith
{
return new FqnBeginsWith($name);
}

public static function and(Criteria ...$criterias): AndCriteria
{
return new AndCriteria(...$criterias);
Expand Down
33 changes: 33 additions & 0 deletions lib/Model/Query/Criteria/FqnBeginsWith.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Phpactor\Indexer\Model\Query\Criteria;

use Phpactor\Indexer\Model\Query\Criteria;
use Phpactor\Indexer\Model\Record;
use Phpactor\Indexer\Model\Record\HasFullyQualifiedName;

class FqnBeginsWith extends Criteria
{
/**
* @var string
*/
private $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function isSatisfiedBy(Record $record): bool
{
if (!$this->name) {
return false;
}

if (!$record instanceof HasFullyQualifiedName) {
return false;
}

return 0 === strpos($record->fqn()->__toString(), $this->name);
}
}
48 changes: 48 additions & 0 deletions tests/Extension/Command/IndexSearchCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Phpactor\Indexer\Tests\Extension\Command;

use Generator;
use Phpactor\Indexer\Tests\IntegrationTestCase;
use Symfony\Component\Process\Process;

class IndexSearchCommandTest extends IntegrationTestCase
{
/**
* @dataProvider provideQuery
*/
public function testQueryIndex(array $args = []): void
{
$this->initProject();

$process = new Process(array_merge([
__DIR__ . '/../../../bin/console',
'index:search',
], $args), $this->workspace()->path());
$process->mustRun();
self::assertEquals(0, $process->getExitCode());
}

/**
* @return Generator<mixed>
*/
public function provideQuery(): Generator
{
yield 'all' => [
[
'--limit=1'
]
];

yield 'classes' => [
[
'--is-class',
'--is-function',
'--short-name=Foo',
'--short-name-begins=Foo',
'--fqn-begins=Foo',
'--limit=1'
]
];
}
}
6 changes: 4 additions & 2 deletions tests/Unit/Model/MemoryUsageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ class MemoryUsageTest extends TestCase
{
public function testMemoryLimit(): void
{
$limit = MemoryUsage::create()->memoryLimit();
self::assertIsInt($limit);
MemoryUsage::create()->memoryLimit();

// the result is system dependent
$this->addToAssertionCount(1);
}

public function testMemoryUsage(): void
Expand Down
47 changes: 47 additions & 0 deletions tests/Unit/Model/Query/Criteria/FqnBeginsWithTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Phpactor\Indexer\Tests\Unit\Model\Query\Criteria;

use PHPUnit\Framework\TestCase;
use Phpactor\Indexer\Model\Query\Criteria;
use Phpactor\Indexer\Model\Query\Criteria\ShortNameBeginsWith;
use Phpactor\Indexer\Model\Record\ClassRecord;

class FqnBeginsWithTest extends TestCase
{
public function testNotMatchesEmpty(): void
{
$record = ClassRecord::fromName('Foobar\\Barfoo');
self::assertTrue(Criteria::fqnBeginsWith('Foobar')->isSatisfiedBy($record));
}

public function testMatchesExact(): void
{
$record = ClassRecord::fromName('Foobar\\Barfoo');
self::assertTrue(Criteria::fqnBeginsWith('Foobar\\Barfoo')->isSatisfiedBy($record));
}

public function testNotMatches(): void
{
$record = ClassRecord::fromName('Foobar\\Bazfoo');
self::assertFalse(Criteria::fqnBeginsWith('Barfoo')->isSatisfiedBy($record));
}

public function testMatchesPartialBeginingWith(): void
{
$record = ClassRecord::fromName('Foobar\\Barfoos');
self::assertTrue(Criteria::fqnBeginsWith('Foo')->isSatisfiedBy($record));
}

public function testNotMatchesPartialEndsWith(): void
{
$record = ClassRecord::fromName('Foobar\\abBarfoo');
self::assertFalse(Criteria::fqnBeginsWith('Barfoo')->isSatisfiedBy($record));
}

public function testMatchesGlobal(): void
{
$record = ClassRecord::fromName('Barfoo');
self::assertTrue(Criteria::fqnBeginsWith('Barfoo')->isSatisfiedBy($record));
}
}