Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@

# docs
/.couscous

# php-cs-fixer
/.php_cs.cache
./.php_cs
5 changes: 5 additions & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
$finder = PhpCsFixer\Finder::create()
->in('lib')
->in('tests')
->exclude([
'Assets/Cache',
'Assets/Projects',
'Assets/Workspace',
])
;

return PhpCsFixer\Config::create()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ private function registerRpc(ContainerBuilder $container)

$container->register('code_transform.rpc.handler.generate_accessor', function (Container $container) {
return new GenerateAccessorHandler(
$container->get(WorseReflectionExtension::SERVICE_REFLECTOR),
$container->get('code_transform.refactor.generate_accessor')
);
}, [ RpcExtension::TAG_RPC_HANDLER => ['name' => GenerateAccessorHandler::NAME] ]);
Expand Down
88 changes: 79 additions & 9 deletions lib/Extension/CodeTransformExtra/Rpc/GenerateAccessorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,40 @@

namespace Phpactor\Extension\CodeTransformExtra\Rpc;

use InvalidArgumentException;
use Phpactor\CodeTransform\Domain\Refactor\GenerateAccessor;
use Phpactor\Extension\Rpc\Response\Input\ListInput;
use Phpactor\MapResolver\Resolver;
use Phpactor\Extension\Rpc\Response\UpdateFileSourceResponse;
use Phpactor\CodeTransform\Domain\SourceCode;
use Phpactor\Extension\Rpc\Handler\AbstractHandler;
use Phpactor\WorseReflection\Core\Reflection\ReflectionClass;
use Phpactor\WorseReflection\Core\Reflection\ReflectionProperty;
use Phpactor\WorseReflection\Reflector;

class GenerateAccessorHandler extends AbstractHandler
{
const NAME = 'generate_accessor';
const PARAM_OFFSET = 'offset';
const PARAM_NAMES = 'names';
const PARAM_SOURCE = 'source';
const PARAM_PATH = 'path';
const PARAM_OFFSET = 'offset';

/**
* @var Reflector
*/
private $reflector;

/**
* @var GenerateAccessor
*/
private $generateAccessor;

public function __construct(
Reflector $reflector,
GenerateAccessor $generateAccessor
) {
$this->reflector = $reflector;
$this->generateAccessor = $generateAccessor;
}

Expand All @@ -33,6 +46,9 @@ public function name(): string

public function configure(Resolver $resolver)
{
$resolver->setDefaults([
self::PARAM_NAMES => null,
]);
$resolver->setRequired([
self::PARAM_PATH,
self::PARAM_SOURCE,
Expand All @@ -42,18 +58,72 @@ public function configure(Resolver $resolver)

public function handle(array $arguments)
{
$sourceCode = $this->generateAccessor->generateAccessor(
SourceCode::fromStringAndPath(
$arguments[self::PARAM_SOURCE],
$arguments[self::PARAM_PATH]
),
$class = $this->class(
$arguments[self::PARAM_SOURCE],
$arguments[self::PARAM_OFFSET]
);

$this->requireInput(ListInput::fromNameLabelChoices(
self::PARAM_NAMES,
sprintf('Properties from "%s"', $class->name()),
$this->propertiesChoices($class)
)->withMultiple(true));

if ($this->hasMissingArguments($arguments)) {
return $this->createInputCallback($arguments);
}

$originalSource = $arguments[self::PARAM_SOURCE];
$newSource = SourceCode::fromStringAndPath($originalSource, $arguments[self::PARAM_PATH]);

foreach ((array) $arguments[self::PARAM_NAMES] as $propertyName) {
$newSource = $this->generateAccessor->generate(
$newSource,
$propertyName,
$arguments[self::PARAM_OFFSET]
);
}

return UpdateFileSourceResponse::fromPathOldAndNewSource(
$sourceCode->path(),
$arguments[self::PARAM_SOURCE],
(string) $sourceCode
$newSource->path(),
$originalSource,
(string) $newSource
);
}

private function class(string $source, int $offset): ReflectionClass
{
$classes = $this->reflector->reflectClassesIn($source);

if ($classes->count() === 1) {
return $classes->first();
}

foreach ($classes as $class) {
$position = $class->position();

if ($position->start() <= $offset && $offset <= $position->end()) {
return $class;
}
}

throw new InvalidArgumentException(
'No classes in source file'
);
}

private function propertiesChoices(ReflectionClass $class): array
{
// Select only those from the current class because the accessor generator
// is not able to work with the parent class at the time
$properties = $class->properties()->belongingTo($class->name());

$propertiesNames = array_map(function (ReflectionProperty $property) {
return $property->name();
}, iterator_to_array($properties));

natsort($propertiesNames);

return array_combine($propertiesNames, $propertiesNames);
}
}
13 changes: 11 additions & 2 deletions lib/Extension/ContextMenu/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@
"offset": "%offset%",
"source": "%source%"
}
},
"generate_accessor": {
"action": "generate_accessor",
"parameters": {
"path": "%path%",
"source": "%source%",
"offset": "%offset%"
}
}
},
"property": {
Expand All @@ -187,8 +195,9 @@
"action": "generate_accessor",
"parameters": {
"path": "%path%",
"offset": "%offset%",
"source": "%source%"
"names": "%symbol%",
"source": "%source%",
"offset": "%offset%"
}
},
"goto_definition": {
Expand Down
6 changes: 5 additions & 1 deletion plugin/phpactor.vim
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,11 @@ function! phpactor#GetClassFullName()
endfunction

function! phpactor#ChangeVisibility()
call phpactor#rpc("change_visibility", { "offset": phpactor#_offset(), "source": phpactor#_source(), "path": expand('%:p')})
call phpactor#rpc("change_visibility", { "offset": phpactor#_offset(), "source": phpactor#_source(), "path": expand('%:p') })
endfunction

function! phpactor#GenerateAccessors()
call phpactor#rpc("generate_accessor", { "source": phpactor#_source(), "path": expand('%:p'), 'offset': phpactor#_offset() })
endfunction

"""""""""""""""""""""""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public function testComplete(string $source, array $expected)
return $one['name'] <=> $two['name'];
});

if (!$expected) {
$this->assertEmpty($suggestions);
}

foreach ($expected as $index => $expectedSuggestion) {
$this->assertArraySubset($expectedSuggestion, $suggestions[$index]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,120 @@

use Phpactor\Extension\Rpc\Handler;
use Phpactor\CodeTransform\Domain\SourceCode;
use Phpactor\Extension\Rpc\Response\InputCallbackResponse;
use Phpactor\Extension\Rpc\Response\Input\ListInput;
use Phpactor\Extension\Rpc\Response\UpdateFileSourceResponse;
use Phpactor\CodeTransform\Domain\Refactor\GenerateAccessor;
use Phpactor\Extension\CodeTransformExtra\Rpc\GenerateAccessorHandler;
use Phpactor\Tests\Unit\Extension\Rpc\HandlerTestCase;
use Phpactor\WorseReflection\ReflectorBuilder;

class GenerateAccessorHandlerTest extends HandlerTestCase
{
const SOURCE = '<?php echo "foo";';
const SOURCE = <<<'EOT'
<?php

class Dummy
{
private $foo;
public $bar;
}
EOT;
const PATH = '/path/to';
const OFFSET = 1234;
const CONSTANT_NAME = 'FOOBAR';
const FOO_NAME = 'foo';
const BAR_NAME = 'bar';
const PROPERTIES_CHOICES = [self::FOO_NAME => self::FOO_NAME, self::BAR_NAME => self::BAR_NAME];
const GENERATE_ACCESSOR_ACTION = 'generate_accessor';
const CURSOR_OFFSET = 57;

/**
* @var GenerateAccessor
*/
private $generateAccessor;

/**
* @var Reflector
*/
private $reflector;

public function setUp()
{
$this->reflector = ReflectorBuilder::create()->addSource(self::SOURCE)->build();
$this->generateAccessor = $this->prophesize(GenerateAccessor::class);
}

public function createHandler(): Handler
{
return new GenerateAccessorHandler($this->generateAccessor->reveal());
return new GenerateAccessorHandler(
$this->reflector,
$this->generateAccessor->reveal()
);
}

public function testGenerateAccessor()
public function testSuggestsPossibleProperties()
{
$this->generateAccessor->generateAccessor(
self::SOURCE,
self::OFFSET
)->willReturn(SourceCode::fromStringAndPath('asd', '/path'));
$action = $this->handle(self::GENERATE_ACCESSOR_ACTION, [
'source' => self::SOURCE,
'path' => self::PATH,
'offset' => self::CURSOR_OFFSET,
]);

$this->assertInstanceOf(InputCallbackResponse::class, $action);

$inputs = $action->inputs();
$input = reset($inputs);

$this->assertInstanceOf(ListInput::class, $input);

$this->assertEquals(self::PROPERTIES_CHOICES, $input->choices());
}

public function testGenerateAccessorFromAPropertyName()
{
$oldSource = SourceCode::fromStringAndPath(self::SOURCE, self::PATH);
$newSource = SourceCode::fromStringAndPath('asd', self::PATH);

$this->generateAccessor->generate($oldSource, self::FOO_NAME, self::CURSOR_OFFSET)
->willReturn($newSource)
->shouldBeCalledTimes(1);

$action = $this->handle(self::GENERATE_ACCESSOR_ACTION, [
'source' => self::SOURCE,
'path' => self::PATH,
'names' => self::FOO_NAME,
'offset' => self::CURSOR_OFFSET,
]);

$this->assertInstanceof(UpdateFileSourceResponse::class, $action);
$this->assertSame((string) $oldSource, $action->oldSource());
$this->assertSame((string) $newSource, $action->newSource());
$this->assertSame(self::PATH, $action->path());
}

public function testGenerateAccessorsFromMultiplePropertyName()
{
$oldSource = SourceCode::fromStringAndPath(self::SOURCE, self::PATH);

$temporarySource = SourceCode::fromStringAndPath('asd', self::PATH);
$this->generateAccessor->generate($oldSource, self::FOO_NAME, self::CURSOR_OFFSET)
->willReturn($temporarySource)
->shouldBeCalledTimes(1);

$newSource = SourceCode::fromStringAndPath((string) $temporarySource, self::PATH);
$this->generateAccessor->generate($temporarySource, self::BAR_NAME, self::CURSOR_OFFSET)
->willReturn($newSource)
->shouldBeCalledTimes(1);

$action = $this->handle(GenerateAccessorHandler::NAME, [
$action = $this->handle(self::GENERATE_ACCESSOR_ACTION, [
'source' => self::SOURCE,
'path' => self::PATH,
'offset' => self::OFFSET
'names' => [self::FOO_NAME, self::BAR_NAME],
'offset' => self::CURSOR_OFFSET,
]);

$this->assertInstanceof(UpdateFileSourceResponse::class, $action);
$this->assertSame((string) $oldSource, $action->oldSource());
$this->assertSame((string) $temporarySource, $action->newSource());
$this->assertSame(self::PATH, $action->path());
}
}