Skip to content
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
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,22 @@ Features
- Resolves variable types.
- Resolves chained object calls (providing return / parameter types are supplied).
- Simple omni complete VIM plugin (use it with [YouCompleteMe](https://github.com/Valloric/YouCompleteMe)
- Contextual code generation

Limitations
-----------
Code Generation
---------------

Generate code snippets for a class using the `generate:snippet` command.

### Implement missing methods

Implements any missing methods, e.g. abstract and interface methods.

```bash
$ phpactor generate:snippet implement_missing_methods tests/System/SystemTestCase.php
```

- Only works for class scopes.
- Requires composer.
Will return, as a single string, the missing methods.

Why?
----
Expand All @@ -53,6 +63,12 @@ set omnifunc=phpactor#complete
let g:phpactor#phpactor_path="/home/daniel/www/dantleech/phpactor/bin/phpactor"
```

Key bindings:

```
nnoremap <silent><leader>mm :call phpactor#generate_snippet("implement_missing_methods")<CR>
```

TODO
----

Expand Down
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
"name": "dantleech/phpactor",
"minimum-stability": "dev",
"require": {
"symfony/console": "~2.7",
"symfony/console": "~2.7|^3.0",
"roave/better-reflection": "dev-variable_collector",
"phpbench/container": "^1.0"
"phpbench/container": "^1.0",
"sylius/registry": "^0.19.0"
},
"require-dev": {
"phpunit/phpunit": "^5.2"
"phpunit/phpunit": "^5.2",
"symfony/process": "^2.7|^3.0"
},
"repositories": [
{ "type": "vcs", "url": "https://github.com/dantleech/BetterReflection" }
Expand Down
41 changes: 41 additions & 0 deletions lib/CodeContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Phpactor;

class CodeContext
{
private $source;
private $offset;
private $path;

private function __construct()
{
}

public static function create(string $path = null, string $source, int $offset)
{
$context = new self();
$context->path = $path;
$context->source = $source;
$context->offset = $offset;

return $context;
}

public function getSource(): string
{
return $this->source;
}

public function getOffset(): int
{
return $this->offset;
}

public function getPath(): string
{
return $this->path;
}
}
6 changes: 4 additions & 2 deletions lib/Complete/Completer.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Phpactor\Complete\ScopeResolver;
use Phpactor\Complete\ScopeFactory;
use Phpactor\Complete\Suggestions;
use Phpactor\CodeContext;

class Completer
{
Expand All @@ -28,10 +29,11 @@ public function __construct(ScopeFactory $scopeFactory, array $providers)
$this->scopeFactory = $scopeFactory;
}

public function complete(string $source, int $offset): Suggestions
public function complete(CodeContext $codeContext): Suggestions
{
$scope = $this->scopeFactory->create($source, $offset);
$scope = $this->scopeFactory->create($codeContext);
$suggestions = new Suggestions();

foreach ($this->providers as $provider) {
if (false === $provider->canProvideFor($scope)) {
continue;
Expand Down
9 changes: 5 additions & 4 deletions lib/Complete/ScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
use Phpactor\Complete\ScopeResolver;
use PhpParser\ParserFactory;
use PhpParser\Lexer;
use Phpactor\CodeContext;

class ScopeFactory
{
public function create($source, $offset): Scope
public function create(CodeContext $codeContext): Scope
{
$source = $this->fixSource($source, $offset);
$source = $this->fixSource($codeContext->getSource());

$lexer = new Lexer([ 'usedAttributes' => [ 'comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos' ] ]);
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, $lexer, []);
$stmts = $parser->parse($source);

foreach ($stmts as $stmt) {
$scope = (new ScopeResolver())->__invoke($stmt, $offset);
$scope = (new ScopeResolver())->__invoke($stmt, $codeContext->getOffset());

if ($scope) {
return $scope;
Expand All @@ -26,7 +27,7 @@ public function create($source, $offset): Scope


throw new \InvalidArgumentException(sprintf(
'Could not resolve scope for source with offset "%s"', $offset
'Could not resolve scope for source with offset "%s"', $codeContext->getOffset()
));
}

Expand Down
24 changes: 5 additions & 19 deletions lib/Console/Command/CompleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use Symfony\Component\Console\Input\InputArgument;
use Phpactor\Reflection\ReflectorInterface;
use Phpactor\Complete\Completer;
use Phpactor\Util\FileUtil;
use Phpactor\CodeContext;

class CompleteCommand extends Command
{
Expand All @@ -29,29 +31,13 @@ public function configure()
{
$this->setName('complete');
$this->setDescription('Explain a class by its class FQN or filename');
$this->addArgument('offset', InputArgument::REQUIRED);
$this->addArgument('fqnOrFname', InputArgument::OPTIONAL, 'Fully qualified class name or filename');
Handler\CodeContextHandler::configure($this);
}

public function execute(InputInterface $input, OutputInterface $output)
{
$offset = $input->getArgument('offset');

$name = $input->getArgument('fqnOrFname');

if ($name) {
$contents = file_get_contents($name);
} else {
$contents = '';
while ($line = fgets(STDIN)) {
$contents .= $line;
}
}
file_put_contents('foobar', $contents);

$completions = $this->completer->complete($contents, $offset);

$context = Handler\CodeContextHandler::contextFromInput($input);
$completions = $this->completer->complete($context);
$output->writeln($out = json_encode($completions->all(), JSON_PRETTY_PRINT));
file_put_contents('foobar-out', $out);
}
}
11 changes: 9 additions & 2 deletions lib/Console/Command/ExplainCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ class ExplainCommand extends Command
*/
private $reflector;

/**
* @var ClassUtil
*/
private $classUtil;

public function __construct(
ClassReflector $reflector
ClassReflector $reflector,
ClassUtil $classUtil
)
{
parent::__construct();
$this->reflector = $reflector;
$this->classUtil = $classUtil;
}

public function configure()
Expand All @@ -48,6 +55,6 @@ private function reflect($name)
return $this->reflector->reflect($name);
}

return $this->reflector->reflect(ClassUtil::getClassNameFromFile($name));
return $this->reflector->reflect($this->classUtil->getClassNameFromFile($name));
}
}
42 changes: 42 additions & 0 deletions lib/Console/Command/GenerateSnippetCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Phpactor\Console\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Phpactor\Util\ClassUtil;
use BetterReflection\Reflector\ClassReflector;
use Symfony\Component\Console\Input\InputArgument;
use Phpactor\Util\FileUtil;
use Phpactor\Generation\Snippet\ImplementMissingMethodsGenerator;
use Phpactor\Generation\SnippetGeneratorRegistry;

class GenerateSnippetCommand extends Command
{
/**
* @var SnippetGeneratorRegistry
*/
private $registry;

public function __construct(SnippetGeneratorRegistry $registry)
{
parent::__construct();
$this->registry = $registry;
}

public function configure()
{
$this->setName('generate:snippet');
$this->addArgument('generator', InputArgument::REQUIRED, 'Name of snippet generator');
Handler\CodeContextHandler::configure($this);
}

public function execute(InputInterface $input, OutputInterface $output)
{
$context = Handler\CodeContextHandler::contextFromInput($input);
$generator = $this->registry->get($input->getArgument('generator'));
$snippet = $generator->generate($context);
$output->write($snippet);
}
}
37 changes: 37 additions & 0 deletions lib/Console/Command/Handler/CodeContextHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Phpactor\Console\Command\Handler;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Phpactor\CodeContext;
use Phpactor\Util\FileUtil;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;

class CodeContextHandler
{
public static function configure(Command $command)
{
$command->addOption('offset', null, InputOption::VALUE_OPTIONAL, 0);
$command->addArgument('fqnOrFname', InputArgument::OPTIONAL, 'Fully qualified class name or filename');
}

public static function contextFromInput(InputInterface $input)
{
$offset = $input->getOption('offset');
$name = $input->getArgument('fqnOrFname');

if ($name) {
FileUtil::assertExists($name);
$contents = file_get_contents($name);
} else {
$contents = '';
while ($line = fgets(STDIN)) {
$contents .= $line;
}
}

return CodeContext::create($name, $contents, (int) $offset);
}
}
39 changes: 38 additions & 1 deletion lib/Extension/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
use BetterReflection\SourceLocator\Type\StringSourceLocator;
use BetterReflection\SourceLocator\Type\AggregateSourceLocator;
use BetterReflection\SourceLocator\Type\PhpInternalSourceLocator;
use Phpactor\Console\Command\GenerateSnippetCommand;
use Phpactor\Util\ClassUtil;
use Phpactor\Generation\Snippet\ImplementMissingMethodsGenerator;
use Phpactor\Generation\SnippetGeneratorRegistry;

class CoreExtension implements ExtensionInterface
{
Expand All @@ -40,6 +44,7 @@ public function getDefaultConfig()
public function load(Container $container)
{
$this->registerComplete($container);
$this->registerGeneration($container);
$this->registerConsole($container);
$this->registerStorage($container);
$this->registerMisc($container);
Expand Down Expand Up @@ -114,6 +119,10 @@ private function registerMisc(Container $container)

return new ClassReflector(new AggregateSourceLocator($locators));
});

$container->register('util.class', function () {
return new ClassUtil();
});
}

private function registerConsole(Container $container)
Expand All @@ -123,17 +132,45 @@ private function registerConsole(Container $container)
$application->addCommands([
$container->get('command.explain'),
$container->get('command.complete'),
$container->get('command.generate'),
]);

return $application;
});

$container->register('command.generate', function (Container $container) {
return new GenerateSnippetCommand($container->get('generator.registry.snippet'));
});

$container->register('command.complete', function (Container $container) {
return new CompleteCommand($container->get('completer'));
});

$container->register('command.explain', function (Container $container) {
return new ExplainCommand($container->get('reflector'));
return new ExplainCommand(
$container->get('reflector'),
$container->get('util.class')
);
});
}

private function registerGeneration(Container $container)
{
$container->register('generator.registry.snippet', function (Container $container) {
$registry = new SnippetGeneratorRegistry();
$registry->register(
'implement_missing_methods',
$container->get('generator.snippet.implement_missing_methods')
);

return $registry;
});

$container->register('generator.snippet.implement_missing_methods', function(Container $container) {
return new ImplementMissingMethodsGenerator(
$container->get('reflector'),
$container->get('util.class')
);
});
}
}
Loading