Skip to content

Commit

Permalink
Merge pull request #348 from Harfusha/cake4
Browse files Browse the repository at this point in the history
Backported support for more complex array annotations (#344) and for multiline annotation (#345) to Cake4.
  • Loading branch information
dereuromark authored Mar 5, 2024
2 parents f4c3c8e + e212e9f commit aaa362a
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 33 deletions.
1 change: 1 addition & 0 deletions config/app.example.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@
],
// If a custom directory should be used, defaults to TMP otherwise
'codeCompletionPath' => null,
'codeCompletionReturnType' => null, // Auto-detect based on controller/component, set to true/false to force one mode.
],
];
31 changes: 29 additions & 2 deletions src/Annotator/AbstractAnnotator.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,14 +485,41 @@ protected function parseExistingAnnotations(File $file, int $closeTagIndex, arra
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode $valueNode */
$valueNode = static::getValueNode($tokens[$i]['content'], $content);
if ($valueNode instanceof InvalidTagValueNode) {
continue;
$multilineFixed = false;
for ($p = $i + 3; $p < $closeTagIndex; $p++) {
if ($tokens[$p]['type'] === 'T_DOC_COMMENT_TAG') {
break;
}

if ($tokens[$p]['type'] !== 'T_DOC_COMMENT_STRING') {
continue;
}

$content .= $tokens[$p]['content'];
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode $valueNode */
$valueNode = static::getValueNode($tokens[$i]['content'], $content);
if (!($valueNode instanceof InvalidTagValueNode)) {
$multilineFixed = true;

break;
}
}

if (!$multilineFixed || $valueNode instanceof InvalidTagValueNode) {
continue;
}
}

$returnTypes = $this->valueNodeParts($valueNode);
$typeString = $this->renderUnionTypes($returnTypes);

$tag = $tokens[$i]['content'];
$content = mb_substr($content, mb_strlen($typeString) + 1);
$variablePos = strpos($content, ' $');
if ($tag === VariableAnnotation::TAG && $variablePos) {
$content = mb_substr($content, $variablePos + 1);
} else {
$content = mb_substr($content, mb_strlen($typeString) + 1);
}

$annotation = AnnotationFactory::createOrFail($tag, $typeString, $content, $classNameIndex);
if ($this->getConfig(static::CONFIG_REMOVE) && $tag === VariableAnnotation::TAG && $this->varInUse($tokens, $closeTagIndex, $content)) {
Expand Down
78 changes: 55 additions & 23 deletions src/CodeCompletion/Task/ControllerEventsTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace IdeHelper\CodeCompletion\Task;

use Cake\Core\Configure;

class ControllerEventsTask implements TaskInterface {

/**
Expand All @@ -20,43 +22,73 @@ public function type(): string {
* @return string
*/
public function create(): string {
$events = <<<'TXT'
public function startup(EventInterface $event) {
/** @var bool|null $returnType */
$returnType = Configure::read('IdeHelper.codeCompletionReturnType');

$controllerEvents = $this->events($returnType ?? false);
$componentEvents = $this->events($returnType ?? true);

return <<<CODE
use Cake\Event\EventInterface;
use Cake\Http\Response;
if (false) {
class Controller {
$controllerEvents
}
class Component {
$componentEvents
}
}
CODE;
}

/**
* @param bool $returnType
*
* @return string
*/
protected function events(bool $returnType): string {
$type = null;
$docBlock = null;
if ($returnType) {
$type = ': ' . '\Cake\Http\Response|null';
} else {
$docBlock = <<<TXT
/**
* @param \Cake\Event\EventInterface \$event
*
* @return \Cake\Http\Response|null|void
*/
TXT;
$docBlock = trim($docBlock) . PHP_EOL . str_repeat("\t", 2);
}

$events = <<<TXT
{$docBlock}public function startup(EventInterface \$event)$type {
return null;
}
public function beforeFilter(EventInterface $event) {
{$docBlock}public function beforeFilter(EventInterface \$event)$type {
return null;
}
public function beforeRender(EventInterface $event) {
{$docBlock}public function beforeRender(EventInterface \$event)$type {
return null;
}
public function afterFilter(EventInterface $event) {
{$docBlock}public function afterFilter(EventInterface \$event)$type {
return null;
}
public function shutdown(EventInterface $event) {
{$docBlock}public function shutdown(EventInterface \$event)$type {
return null;
}
public function beforeRedirect(EventInterface $event, $url, Response $response) {
{$docBlock}public function beforeRedirect(EventInterface \$event, \$url, Response \$response)$type {
return null;
}
TXT;

return <<<CODE
use Cake\Event\EventInterface;
use Cake\Http\Response;
if (false) {
abstract class Controller {
$events
}
abstract class Component {
$events
}
}
CODE;
return $events;
}

}
54 changes: 54 additions & 0 deletions tests/TestCase/Annotator/TemplateAnnotatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,60 @@ public function testAnnotateWithFollowingInline() {
$this->assertTextContains(' -> 1 annotation added.', $output);
}

/**
* Tests that a docblock with arrays in different types, e.g. shape.
*
* @return void
*/
public function testAnnotateWithShapedArray() {
$annotator = $this->_getAnnotatorMock([]);

$expectedContent = str_replace("\r\n", "\n", file_get_contents(TEST_FILES . 'templates/array.php'));
$callback = function($value) use ($expectedContent) {
$value = str_replace(["\r\n", "\r"], "\n", $value);
if ($value !== $expectedContent) {
$this->_displayDiff($expectedContent, $value);
}

return $value === $expectedContent;
};
$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));

$path = TEST_ROOT . 'templates/Foos/array.php';
$annotator->annotate($path);

$output = $this->out->output();

$this->assertTextContains(' -> 1 annotation added.', $output);
}

/**
* Tests that a multiline array is parsed completly.
*
* @return void
*/
public function testAnnotateWithMultilineArray() {
$annotator = $this->_getAnnotatorMock([]);

$expectedContent = str_replace("\r\n", "\n", file_get_contents(TEST_FILES . 'templates/multiline.php'));
$callback = function($value) use ($expectedContent) {
$value = str_replace(["\r\n", "\r"], "\n", $value);
if ($value !== $expectedContent) {
$this->_displayDiff($expectedContent, $value);
}

return $value === $expectedContent;
};
$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));

$path = TEST_ROOT . 'templates/Foos/multiline.php';
$annotator->annotate($path);

$output = $this->out->output();

$this->assertTextContains(' -> 1 annotation added.', $output);
}

/**
* @param array $params
* @return \IdeHelper\Annotator\TemplateAnnotator|\PHPUnit\Framework\MockObject\MockObject
Expand Down
46 changes: 38 additions & 8 deletions tests/TestCase/CodeCompletion/Task/ControllerEventsTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,44 +33,74 @@ public function testCollect() {
use Cake\Http\Response;
if (false) {
abstract class Controller {
class Controller {
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function startup(EventInterface $event) {
return null;
}
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function beforeFilter(EventInterface $event) {
return null;
}
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function beforeRender(EventInterface $event) {
return null;
}
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function afterFilter(EventInterface $event) {
return null;
}
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function shutdown(EventInterface $event) {
return null;
}
/**
* @param \Cake\Event\EventInterface $event
*
* @return \Cake\Http\Response|null|void
*/
public function beforeRedirect(EventInterface $event, $url, Response $response) {
return null;
}
}
abstract class Component {
public function startup(EventInterface $event) {
class Component {
public function startup(EventInterface $event): \Cake\Http\Response|null {
return null;
}
public function beforeFilter(EventInterface $event) {
public function beforeFilter(EventInterface $event): \Cake\Http\Response|null {
return null;
}
public function beforeRender(EventInterface $event) {
public function beforeRender(EventInterface $event): \Cake\Http\Response|null {
return null;
}
public function afterFilter(EventInterface $event) {
public function afterFilter(EventInterface $event): \Cake\Http\Response|null {
return null;
}
public function shutdown(EventInterface $event) {
public function shutdown(EventInterface $event): \Cake\Http\Response|null {
return null;
}
public function beforeRedirect(EventInterface $event, $url, Response $response) {
public function beforeRedirect(EventInterface $event, $url, Response $response): \Cake\Http\Response|null {
return null;
}
}
Expand Down
22 changes: 22 additions & 0 deletions tests/test_app/templates/Foos/array.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
/**
* @var \TestApp\View\AppView $this
* @var array $x
* @var array<int> $ints
* @var array{a: int, b: string|null}|null $shaped
*/
foreach ($x as $y) {
echo $y;
}
foreach ($foo as $int) {
echo $int;
}
?>
<div>
<?php foreach ($ints as $int) {
echo $int;
} ?>
<?php foreach ($shaped as $x) {
echo h($x);
} ?>
</div>
34 changes: 34 additions & 0 deletions tests/test_app/templates/Foos/multiline.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @var \TestApp\View\AppView $this
* @var array $x
* @var array<int> $ints
* @var array{
* a: int,
* b: string|null
* }|null $shaped
* @var array{
* c: array{
* d: int|string,
* e: string|null
* }
* } $nested
*/
foreach ($x as $y) {
echo $y;
}
foreach ($foo as $int) {
echo $int;
}
?>
<div>
<?php foreach ($ints as $int) {
echo $int;
} ?>
<?php foreach ($shaped as $x) {
echo h($x);
} ?>
<?php foreach ($nested['c'] as $subArray) {
echo h($x);
} ?>
</div>
23 changes: 23 additions & 0 deletions tests/test_files/templates/array.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* @var \TestApp\View\AppView $this
* @var array $x
* @var array<int> $ints
* @var array{a: int, b: string|null}|null $shaped
* @var mixed $foo
*/
foreach ($x as $y) {
echo $y;
}
foreach ($foo as $int) {
echo $int;
}
?>
<div>
<?php foreach ($ints as $int) {
echo $int;
} ?>
<?php foreach ($shaped as $x) {
echo h($x);
} ?>
</div>
Loading

0 comments on commit aaa362a

Please sign in to comment.