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
59 changes: 53 additions & 6 deletions src/PhpGenerator/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
use PhpParser;
use PhpParser\Node;
use PhpParser\ParserFactory;
use function strlen;
use function substr_replace;


/**
Expand Down Expand Up @@ -197,8 +199,7 @@ private function loadMethodBodies(\ReflectionClass $from): array
foreach ($nodeFinder->findInstanceOf($class, Node\Stmt\ClassMethod::class) as $method) {
/** @var Node\Stmt\ClassMethod $method */
if ($method->stmts) {
$start = $method->stmts[0]->getAttribute('startFilePos');
$body = substr($code, $start, end($method->stmts)->getAttribute('endFilePos') - $start + 1);
$body = $this->extractBodyWithFQN($nodeFinder, $code, $method->stmts);
$bodies[$method->name->toString()] = Helpers::indentPhp($body, -2);
}
}
Expand All @@ -213,17 +214,62 @@ private function loadFunctionBody(\ReflectionFunction $from): string
}

[$code, $stmts] = $this->parse($from);

$nodeFinder = new PhpParser\NodeFinder;
/** @var Node\Stmt\Function_ $function */
$function = (new PhpParser\NodeFinder)->findFirst($stmts, function (Node $node) use ($from) {
$function = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) {
return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $from->name;
});

$start = $function->stmts[0]->getAttribute('startFilePos');
$body = substr($code, $start, end($function->stmts)->getAttribute('endFilePos') - $start + 1);
$body = $this->extractBodyWithFQN($nodeFinder, $code, $function->stmts);
return Helpers::indentPhp($body, -1);
}


/**
* @param PhpParser\NodeFinder $nodeFinder
* @param string $originalCode
* @param Node[] $statements
* @return string
*/
private function extractBodyWithFQN(PhpParser\NodeFinder $nodeFinder, string $originalCode, array $statements): string
{
$resolvedFQNames = [];
//collect all name-nodes with resolved fully-qualified name
foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $namedNode) {
if ($namedNode->hasAttribute('resolvedName')
&& $namedNode->getAttribute('resolvedName') instanceof Node\Name\FullyQualified) {
$resolvedFQNames[] = $namedNode->getAttributes();
}
}

$start = $statements[0]->getAttribute('startFilePos');
$body = substr($originalCode, $start, end($statements)->getAttribute('endFilePos') - $start + 1);

//if there are some resolved names in original code then replace them with fqn
if ($resolvedFQNames !== []) {
//sort collected resolved names by position in file
usort($resolvedFQNames, function ($a, $b) {
return $a['startFilePos'] <=> $b['startFilePos'];
});
$correctiveOffset = -$start;
//replace changes body length so we need correct offset
foreach ($resolvedFQNames as $resolvedFQName) {
$replacement = $resolvedFQName['resolvedName']->toCodeString();
$replacingStringLength = $resolvedFQName['endFilePos'] - $resolvedFQName['startFilePos'] + 1;
$body = substr_replace(
$body,
$replacement,
$correctiveOffset + $resolvedFQName['startFilePos'],
$replacingStringLength
);
$correctiveOffset += strlen($replacement) - $replacingStringLength;
}
}
return $body;
}


private function parse($from): array
{
$file = $from->getFileName();
Expand All @@ -240,7 +286,8 @@ private function parse($from): array
$stmts = $parser->parse($code);

$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
//set 'replaceNodes' option to false to mark resolved names instead of replace
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false]));
$stmts = $traverser->traverse($stmts);

return [$code, $stmts];
Expand Down
4 changes: 2 additions & 2 deletions tests/PhpGenerator/GlobalFunction.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require __DIR__ . '/../bootstrap.php';
/** global */
function func(stdClass $a, $b = null)
{
echo 'hello';
echo sprintf('hello, %s', 'world');
return 1;
}

Expand All @@ -34,7 +34,7 @@ same(
*/
function func(stdClass $a, $b = null)
{
echo \'hello\';
echo \\sprintf(\'hello, %s\', \'world\');
return 1;
}
', (string) $function);
10 changes: 5 additions & 5 deletions tests/PhpGenerator/expected/ClassType.from.bodies.expect
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ abstract class Class7

public function long()
{
if ($member instanceof Method) {
if ($member instanceof \Abc\Method) {
$s = [1, 2, 3];
}
/*
$this->methods[$member->getName()] = $member;
*/
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
throw new \Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
}


Expand All @@ -56,8 +56,8 @@ abstract class Class7
/** multi
line
comment */

if ($member instanceof Method) {
// Alias Method will not be resolved in comment
if ($member instanceof \Abc\Method) {
$s1 = '
a
b
Expand Down Expand Up @@ -87,6 +87,6 @@ a
c
<?php
}
throw new Nette\InvalidArgumentException();
throw new \Nette\InvalidArgumentException();
}
}
4 changes: 3 additions & 1 deletion tests/PhpGenerator/fixtures/class-body.phpf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ declare(strict_types=1);

namespace Abc;

use Nette;

abstract class Class7
{
abstract function abstractFun();
Expand Down Expand Up @@ -51,7 +53,7 @@ abstract class Class7
/** multi
line
comment */

// Alias Method will not be resolved in comment
if ($member instanceof Method) {
$s1 = '
a
Expand Down